/*
 * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
 * agreements. See the NOTICE file distributed with this work for additional information regarding
 * copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance with the License. You may obtain a
 * copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License
 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
 * or implied. See the License for the specific language governing permissions and limitations under
 * the License.
 */
package org.apache.geode;

import java.io.DataInput;
import java.io.DataOutput;
import java.io.File;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.lang.reflect.Array;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.IdentityHashMap;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.Stack;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.Vector;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

import org.apache.logging.log4j.Logger;

import org.apache.geode.admin.RegionNotFoundException;
import org.apache.geode.annotations.internal.MakeNotStatic;
import org.apache.geode.cache.Cache;
import org.apache.geode.cache.CacheFactory;
import org.apache.geode.cache.Region;
import org.apache.geode.internal.HeapDataOutputStream;
import org.apache.geode.internal.InternalDataSerializer;
import org.apache.geode.internal.ObjToByteArraySerializer;
import org.apache.geode.internal.cache.CachedDeserializable;
import org.apache.geode.internal.cache.EventID;
import org.apache.geode.internal.cache.GemFireCacheImpl;
import org.apache.geode.internal.cache.InternalCache;
import org.apache.geode.internal.cache.tier.sockets.ClientProxyMembershipID;
import org.apache.geode.internal.logging.log4j.LogMarker;
import org.apache.geode.internal.offheap.StoredObject;
import org.apache.geode.internal.serialization.DSCODE;
import org.apache.geode.internal.serialization.StaticSerialization;
import org.apache.geode.internal.serialization.Version;
import org.apache.geode.logging.internal.log4j.api.LogService;
import org.apache.geode.pdx.PdxInstance;

/**
 * Provides static helper methods for reading and writing non-primitive data when working with a
 * {@link DataSerializable}. For instance, classes that implement <code>DataSerializable</code> can
 * use the <code>DataSerializer</code> in their <code>toData</code> and <code>fromData</code>
 * methods:
 *
 * <!-- The source code for the Employee class resides in tests/com/examples/ds/Employee.java Please
 * keep the below code snippet in sync with that file. -->
 *
 * <PRE>
 * public class Employee implements DataSerializable {
 *   private int id;
 *   private String name;
 *   private Date birthday;
 *   private Company employer;
 *
 *   public void toData(DataOutput out) throws IOException {
 *     out.writeInt(this.id);
 *     out.writeUTF(this.name);
 *     DataSerializer.writeDate(this.birthday, out);
 *     DataSerializer.writeObject(this.employer, out);
 *   }
 *
 *   public void fromData(DataInput in) throws IOException, ClassNotFoundException {
 *
 *     this.id = in.readInt();
 *     this.name = in.readUTF();
 *     this.birthday = DataSerializer.readDate(in);
 *     this.employer = (Company) DataSerializer.readObject(in);
 *   }
 * }
 *
 * </PRE>
 *
 * <P>
 *
 * Instances of <code>DataSerializer</code> are used to data serialize objects (such as instances of
 * standard Java classes or third-party classes for which the source code is not available) that do
 * not implement the <code>DataSerializable</code> interface.
 *
 * <P>
 *
 * The following <code>DataSerializer</code> data serializes instances of <code>Company</code>. In
 * order for the data serialization framework to consult this custom serializer, it must be
 * {@linkplain #register(Class) registered} with the framework.
 *
 * <!-- The source code for the CompanySerializer class resides in
 * tests/com/examples/ds/CompanySerializer.java Please keep the below code snippet in sync with that
 * file. -->
 *
 * <PRE>
public class CompanySerializer extends DataSerializer {

  static {
    DataSerializer.register(CompanySerializer.class);
  }

  &#47;**
   * May be invoked reflectively if instances of Company are
   * distributed to other VMs.
   *&#47;
  public CompanySerializer() {

  }

  public Class[] getSupportedClasses() {
    return new Class[] { Company.class };
  }
  public int getId() {
    return 42;
  }

  public boolean toData(Object o, DataOutput out)
    throws IOException {
    if (o instanceof Company) {
      Company company = (Company) o;
      out.writeUTF(company.getName());

      // Let's assume that Address is java.io.Serializable
      Address address = company.getAddress();
      writeObject(address, out);
      return true;

    } else {
      return false;
    }
  }

  public Object fromData(DataInput in)
    throws IOException, ClassNotFoundException {

    String name = in.readUTF();
    Address address = (Address) readObject(in);
    return new Company(name, address);
  }
}
 * </PRE>
 *
 * Just like {@link Instantiator}s, a <code>DataSerializer</code> may be sent to other members of
 * the distributed system when it is {@linkplain #register(Class) registered}. The data
 * serialization framework does not require that a <code>DataSerializer</code> be
 * {@link Serializable}, but it does require that it provide a {@linkplain #DataSerializer()
 * zero-argument constructor}.
 *
 * @see #writeObject(Object, DataOutput)
 * @see #readObject
 *
 * @since GemFire 3.5
 */
public abstract class DataSerializer {

  private static final Logger logger = LogService.getLogger();

  /** The eventId of this <code>DataSerializer</code> */
  private EventID eventId;

  /** The originator of this <code>DataSerializer</code> */
  private ClientProxyMembershipID context;

  /**
   * @deprecated Use Boolean.getBoolean("DataSerializer.TRACE_SERIALIZABLE") instead.
   */
  @Deprecated
  protected static final boolean TRACE_SERIALIZABLE =
      Boolean.getBoolean("DataSerializer.TRACE_SERIALIZABLE");

  /* Used to prevent standard Java serialization when sending data to a non-Java client */
  protected static final ThreadLocal<Boolean> DISALLOW_JAVA_SERIALIZATION = new ThreadLocal<>();

  /**
   * Writes an instance of <code>Class</code> to a <code>DataOutput</code>. This method will handle
   * a <code>null</code> value and not throw a <code>NullPointerException</code>.
   *
   * @throws IOException A problem occurs while writing to <code>out</code>
   *
   * @see #readClass
   */
  public static void writeClass(Class<?> c, DataOutput out) throws IOException {

    InternalDataSerializer.checkOut(out);

    if (logger.isTraceEnabled(LogMarker.SERIALIZER_VERBOSE)) {
      logger.trace(LogMarker.SERIALIZER_VERBOSE, "Writing Class {}", c);
    }

    if (c == null || c.isPrimitive()) {
      StaticSerialization.writePrimitiveClass(c, out);
    } else {
      // non-primitive classes have a second CLASS byte
      // if readObject/writeObject is called:
      // the first CLASS byte indicates it's a Class, the second
      // one indicates it's a non-primitive Class
      out.writeByte(DSCODE.CLASS.toByte());
      String cname = c.getName();
      cname = InternalDataSerializer.processOutgoingClassName(cname, out);
      writeString(cname, out);
    }
  }

  /**
   * Writes class name to a <code>DataOutput</code>. This method will handle a <code>null</code>
   * value and not throw a <code>NullPointerException</code>.
   *
   * @throws IOException A problem occurs while writing to <code>out</code>
   *
   * @see #readNonPrimitiveClassName(DataInput)
   */
  public static void writeNonPrimitiveClassName(String className, DataOutput out)
      throws IOException {

    InternalDataSerializer.checkOut(out);

    if (logger.isTraceEnabled(LogMarker.SERIALIZER_VERBOSE)) {
      logger.trace(LogMarker.SERIALIZER_VERBOSE, "Writing Class name {}", className);
    }

    writeString(InternalDataSerializer.processOutgoingClassName(className, out), out);
  }

  /**
   * Reads an instance of <code>Class</code> from a <code>DataInput</code>. The class will be loaded
   * using the {@linkplain Thread#getContextClassLoader current content class loader}. The return
   * value may be <code>null</code>.
   *
   * @throws IOException A problem occurs while reading from <code>in</code>
   * @throws ClassNotFoundException The class cannot be loaded
   */
  public static Class<?> readClass(DataInput in) throws IOException, ClassNotFoundException {

    InternalDataSerializer.checkIn(in);

    byte typeCode = in.readByte();
    if (typeCode == DSCODE.CLASS.toByte()) {
      String className = readString(in);
      return InternalDataSerializer.getCachedClass(className);
    } else {
      return StaticSerialization.decodePrimitiveClass(typeCode);
    }
  }

  /**
   * Reads name of an instance of <code>Class</code> from a <code>DataInput</code>.
   *
   * The return value may be <code>null</code>.
   *
   * @throws IOException A problem occurs while reading from <code>in</code>
   * @see #writeNonPrimitiveClassName(String, DataOutput)
   */
  public static String readNonPrimitiveClassName(DataInput in) throws IOException {

    InternalDataSerializer.checkIn(in);

    return InternalDataSerializer.processIncomingClassName(readString(in));
  }

  /**
   * Writes an instance of Region. A Region is serialized as just a reference to a full path only.
   * It will be recreated on the other end by calling {@link CacheFactory#getAnyInstance} and then
   * calling <code>getRegion</code> on it. This method will handle a <code>null</code> value and not
   * throw a <code>NullPointerException</code>.
   */
  public static void writeRegion(Region<?, ?> rgn, DataOutput out) throws IOException {
    writeString((rgn != null) ? rgn.getFullPath() : null, out);
  }

  /**
   * Reads an instance of Region. A Region is serialized as a reference to a full path only. It is
   * recreated on the other end by calling {@link CacheFactory#getAnyInstance} and then calling
   * <code>getRegion</code> on it. The return value may be <code>null</code>.
   *
   * @param in the input stream
   * @return the Region instance
   * @throws org.apache.geode.cache.CacheClosedException if a cache has not been created or the only
   *         created one is closed.
   * @throws RegionNotFoundException if there is no region by this name in the Cache
   */
  public static <K, V> Region<K, V> readRegion(DataInput in)
      throws IOException, ClassNotFoundException {
    String fullPath = readString(in);
    Region<K, V> rgn = null;
    if (fullPath != null) {
      // use getExisting to fix bug 43151
      rgn = ((Cache) GemFireCacheImpl.getExisting("Needed cache to find region."))
          .getRegion(fullPath);
      if (rgn == null) {
        throw new RegionNotFoundException(
            String.format(
                "Region ' %s ' could not be found while reading a DataSerializer stream",
                fullPath));
      }
    }
    return rgn;
  }

  /**
   * Writes an instance of <code>Date</code> to a <code>DataOutput</code>. Note that even though
   * <code>date</code> may be an instance of a subclass of <code>Date</code>, <code>readDate</code>
   * will always return an instance of <code>Date</code>, <B>not</B> an instance of the subclass. To
   * preserve the class type of <code>date</code>,\ {@link #writeObject(Object, DataOutput)} should
   * be used for data serialization. This method will handle a <code>null</code> value and not throw
   * a <code>NullPointerException</code>.
   *
   * @throws IOException A problem occurs while writing to <code>out</code>
   *
   * @see #readDate
   */
  public static void writeDate(Date date, DataOutput out) throws IOException {

    InternalDataSerializer.checkOut(out);

    if (logger.isTraceEnabled(LogMarker.SERIALIZER_VERBOSE)) {
      logger.trace(LogMarker.SERIALIZER_VERBOSE, "Writing Date {}", date);
    }

    long v;
    if (date == null) {
      v = -1L;
    } else {
      v = date.getTime();
      if (v == -1L) {
        throw new IllegalArgumentException(
            "Dates whose getTime returns -1 can not be DataSerialized.");
      }
    }
    out.writeLong(v);
  }

  /**
   * Reads an instance of <code>Date</code> from a <code>DataInput</code>. The return value may be
   * <code>null</code>.
   *
   * @throws IOException A problem occurs while reading from <code>in</code>
   */
  public static Date readDate(DataInput in) throws IOException {
    InternalDataSerializer.checkIn(in);

    long time = in.readLong();
    Date date = null;
    if (time != -1L) {
      date = new Date(time);
    }

    if (logger.isTraceEnabled(LogMarker.SERIALIZER_VERBOSE)) {
      logger.trace(LogMarker.SERIALIZER_VERBOSE, "Read Date {}", date);
    }

    return date;
  }

  /**
   * Writes an instance of <code>File</code> to a <code>DataOutput</code>. Note that even though
   * <code>file</code> may be an instance of a subclass of <code>File</code>, <code>readFile</code>
   * will always return an instance of <code>File</code>, <B>not</B> an instance of the subclass. To
   * preserve the class type of <code>file</code>, {@link #writeObject(Object, DataOutput)} should
   * be used for data serialization. This method will handle a <code>null</code> value and not throw
   * a <code>NullPointerException</code>.
   *
   * @throws IOException A problem occurs while writing to <code>out</code>
   *
   * @see #readFile
   * @see File#getCanonicalPath
   */
  public static void writeFile(File file, DataOutput out) throws IOException {

    InternalDataSerializer.checkOut(out);

    if (logger.isTraceEnabled(LogMarker.SERIALIZER_VERBOSE)) {
      logger.trace(LogMarker.SERIALIZER_VERBOSE, "Writing File {}", file);
    }

    writeString((file != null) ? file.getCanonicalPath() : null, out);
  }

  /**
   * Reads an instance of <code>File</code> from a <code>DataInput</code>. The return value may be
   * <code>null</code>.
   *
   * @throws IOException A problem occurs while reading from <code>in</code>
   */
  public static File readFile(DataInput in) throws IOException {
    InternalDataSerializer.checkIn(in);

    String s = readString(in);
    File file = null;
    if (s != null) {
      file = new File(s);
    }
    if (logger.isTraceEnabled(LogMarker.SERIALIZER_VERBOSE)) {
      logger.trace(LogMarker.SERIALIZER_VERBOSE, "Read File {}", file);
    }

    return file;
  }

  /**
   * Writes an instance of <code>InetAddress</code> to a <code>DataOutput</code>. The
   * <code>InetAddress</code> is data serialized by writing its {@link InetAddress#getAddress byte}
   * representation to the <code>DataOutput</code>. {@link #readInetAddress} converts the
   * <code>byte</code> representation to an instance of <code>InetAddress</code> using
   * {@link InetAddress#getAddress}. As a result, if <code>address</code> is an instance of a
   * user-defined subclass of <code>InetAddress</code> (that is, not an instance of one of the
   * subclasses from the <code>java.net</code> package), its class will not be preserved. In order
   * to be able to read an instance of the user-defined class,
   * {@link #writeObject(Object, DataOutput)} should be used. This method will handle a
   * <code>null</code> value and not throw a <code>NullPointerException</code>.
   *
   * @throws IOException A problem occurs while writing to <code>out</code>
   *
   * @see #readInetAddress
   */
  public static void writeInetAddress(InetAddress address, DataOutput out) throws IOException {

    InternalDataSerializer.checkOut(out);

    if (logger.isTraceEnabled(LogMarker.SERIALIZER_VERBOSE)) {
      logger.trace(LogMarker.SERIALIZER_VERBOSE, "Writing InetAddress {}", address);
    }

    StaticSerialization.writeInetAddress(address, out);
  }

  /**
   * Reads an instance of <code>InetAddress</code> from a <code>DataInput</code>. The return value
   * may be <code>null</code>.
   *
   * @throws IOException A problem occurs while reading from <code>in</code> or the address read
   *         from <code>in</code> is unknown
   *
   * @see InetAddress#getAddress
   */
  public static InetAddress readInetAddress(DataInput in) throws IOException {

    InternalDataSerializer.checkIn(in);
    InetAddress address = StaticSerialization.readInetAddress(in);

    if (logger.isTraceEnabled(LogMarker.SERIALIZER_VERBOSE)) {
      logger.trace(LogMarker.SERIALIZER_VERBOSE, "Read InetAddress {}", address);
    }

    return address;
  }

  /**
   * Writes an instance of <code>String</code> to a <code>DataOutput</code>. This method will handle
   * a <code>null</code> value and not throw a <code>NullPointerException</code>.
   * <p>
   * As of 5.7 strings longer than 0xFFFF can be serialized.
   *
   * @throws IOException A problem occurs while writing to <code>out</code>
   *
   * @see #readString
   */
  public static void writeString(String value, DataOutput out) throws IOException {

    InternalDataSerializer.checkOut(out);

    final boolean isTraceSerialzerVerbose = logger.isTraceEnabled(LogMarker.SERIALIZER_VERBOSE);
    if (isTraceSerialzerVerbose) {
      logger.trace(LogMarker.SERIALIZER_VERBOSE, "Writing String \"{}\"", value);
    }

    StaticSerialization.writeString(value, out);
  }

  /**
   * Reads an instance of <code>String</code> from a <code>DataInput</code>. The return value may be
   * <code>null</code>.
   *
   * @throws IOException A problem occurs while reading from <code>in</code>
   *
   * @see #writeString
   */
  public static String readString(DataInput in) throws IOException {
    return StaticSerialization.readString(in);
  }

  /**
   * Writes an instance of <code>Boolean</code> to a <code>DataOutput</code>.
   *
   * @throws IOException A problem occurs while writing to <code>out</code>
   * @throws NullPointerException if value is null.
   *
   * @see #readBoolean
   */
  public static void writeBoolean(Boolean value, DataOutput out) throws IOException {

    InternalDataSerializer.checkOut(out);

    if (logger.isTraceEnabled(LogMarker.SERIALIZER_VERBOSE)) {
      logger.trace(LogMarker.SERIALIZER_VERBOSE, "Writing Boolean {}", value);
    }

    out.writeBoolean(value);
  }

  /**
   * Reads an instance of <code>Boolean</code> from a <code>DataInput</code>.
   *
   * @throws IOException A problem occurs while reading from <code>in</code>
   */
  public static Boolean readBoolean(DataInput in) throws IOException {
    InternalDataSerializer.checkIn(in);

    Boolean value = in.readBoolean();
    if (logger.isTraceEnabled(LogMarker.SERIALIZER_VERBOSE)) {
      logger.trace(LogMarker.SERIALIZER_VERBOSE, "Read Boolean {}", value);
    }
    return value;
  }

  /**
   * Writes an instance of <code>Character</code> to a <code>DataOutput</code>.
   *
   * @throws IOException A problem occurs while writing to <code>out</code>
   * @throws NullPointerException if value is null.
   *
   * @see #readCharacter
   */
  public static void writeCharacter(Character value, DataOutput out) throws IOException {

    InternalDataSerializer.checkOut(out);

    if (logger.isTraceEnabled(LogMarker.SERIALIZER_VERBOSE)) {
      logger.trace(LogMarker.SERIALIZER_VERBOSE, "Writing Character {}", value);
    }

    out.writeChar(value);
  }

  /**
   * Reads an instance of <code>Character</code> from a <code>DataInput</code>.
   *
   * @throws IOException A problem occurs while reading from <code>in</code>
   */
  public static Character readCharacter(DataInput in) throws IOException {

    InternalDataSerializer.checkIn(in);

    Character value = in.readChar();
    if (logger.isTraceEnabled(LogMarker.SERIALIZER_VERBOSE)) {
      logger.trace(LogMarker.SERIALIZER_VERBOSE, "Read Character {}", value);
    }
    return value;
  }

  /**
   * Writes an instance of <code>Byte</code> to a <code>DataOutput</code>.
   *
   * @throws IOException A problem occurs while writing to <code>out</code>
   * @throws NullPointerException if value is null.
   *
   * @see #readByte
   */
  public static void writeByte(Byte value, DataOutput out) throws IOException {

    InternalDataSerializer.checkOut(out);

    if (logger.isTraceEnabled(LogMarker.SERIALIZER_VERBOSE)) {
      logger.trace(LogMarker.SERIALIZER_VERBOSE, "Writing Byte {}", value);
    }

    out.writeByte(value);
  }

  /**
   * Reads an instance of <code>Byte</code> from a <code>DataInput</code>.
   *
   * @throws IOException A problem occurs while reading from <code>in</code>
   */
  public static Byte readByte(DataInput in) throws IOException {
    InternalDataSerializer.checkIn(in);

    Byte value = in.readByte();
    if (logger.isTraceEnabled(LogMarker.SERIALIZER_VERBOSE)) {
      logger.trace(LogMarker.SERIALIZER_VERBOSE, "Read Byte {}", value);
    }
    return value;
  }

  /**
   * Writes an instance of <code>Short</code> to a <code>DataOutput</code>.
   *
   * @throws IOException A problem occurs while writing to <code>out</code>
   * @throws NullPointerException if value is null.
   *
   * @see #readShort
   */
  public static void writeShort(Short value, DataOutput out) throws IOException {

    InternalDataSerializer.checkOut(out);

    if (logger.isTraceEnabled(LogMarker.SERIALIZER_VERBOSE)) {
      logger.trace(LogMarker.SERIALIZER_VERBOSE, "Writing Short {}", value);
    }

    out.writeShort(value);
  }

  /**
   * Reads an instance of <code>Short</code> from a <code>DataInput</code>.
   *
   * @throws IOException A problem occurs while reading from <code>in</code>
   */
  public static Short readShort(DataInput in) throws IOException {
    InternalDataSerializer.checkIn(in);

    Short value = in.readShort();
    if (logger.isTraceEnabled(LogMarker.SERIALIZER_VERBOSE)) {
      logger.trace(LogMarker.SERIALIZER_VERBOSE, "Read Short {}", value);
    }
    return value;
  }

  /**
   * Writes an instance of <code>Integer</code> to a <code>DataOutput</code>.
   *
   * @throws IOException A problem occurs while writing to <code>out</code>
   * @throws NullPointerException if value is null.
   *
   * @see #readInteger
   */
  public static void writeInteger(Integer value, DataOutput out) throws IOException {

    InternalDataSerializer.checkOut(out);

    if (logger.isTraceEnabled(LogMarker.SERIALIZER_VERBOSE)) {
      logger.trace(LogMarker.SERIALIZER_VERBOSE, "Writing Integer {}", value);
    }

    out.writeInt(value);
  }

  /**
   * Reads an instance of <code>Integer</code> from a <code>DataInput</code>.
   *
   * @throws IOException A problem occurs while reading from <code>in</code>
   */
  public static Integer readInteger(DataInput in) throws IOException {
    InternalDataSerializer.checkIn(in);

    Integer value = in.readInt();
    if (logger.isTraceEnabled(LogMarker.SERIALIZER_VERBOSE)) {
      logger.trace(LogMarker.SERIALIZER_VERBOSE, "Read Integer {}", value);
    }
    return value;
  }

  /**
   * Writes an instance of <code>Long</code> to a <code>DataOutput</code>.
   *
   * @throws IOException A problem occurs while writing to <code>out</code>
   * @throws NullPointerException if value is null.
   *
   * @see #readLong
   */
  public static void writeLong(Long value, DataOutput out) throws IOException {

    InternalDataSerializer.checkOut(out);

    if (logger.isTraceEnabled(LogMarker.SERIALIZER_VERBOSE)) {
      logger.trace(LogMarker.SERIALIZER_VERBOSE, "Writing Long {}", value);
    }

    out.writeLong(value);
  }

  /**
   * Reads an instance of <code>Long</code> from a <code>DataInput</code>.
   *
   * @throws IOException A problem occurs while reading from <code>in</code>
   */
  public static Long readLong(DataInput in) throws IOException {
    InternalDataSerializer.checkIn(in);

    Long value = in.readLong();
    if (logger.isTraceEnabled(LogMarker.SERIALIZER_VERBOSE)) {
      logger.trace(LogMarker.SERIALIZER_VERBOSE, "Read Long {}", value);
    }
    return value;
  }

  /**
   * Writes an instance of <code>Float</code> to a <code>DataOutput</code>.
   *
   * @throws IOException A problem occurs while writing to <code>out</code>
   * @throws NullPointerException if value is null.
   *
   * @see #readFloat
   */
  public static void writeFloat(Float value, DataOutput out) throws IOException {

    InternalDataSerializer.checkOut(out);

    if (logger.isTraceEnabled(LogMarker.SERIALIZER_VERBOSE)) {
      logger.trace(LogMarker.SERIALIZER_VERBOSE, "Writing Float {}", value);
    }

    out.writeFloat(value);
  }

  /**
   * Reads an instance of <code>Float</code> from a <code>DataInput</code>.
   *
   * @throws IOException A problem occurs while reading from <code>in</code>
   */
  public static Float readFloat(DataInput in) throws IOException {
    InternalDataSerializer.checkIn(in);

    Float value = in.readFloat();
    if (logger.isTraceEnabled(LogMarker.SERIALIZER_VERBOSE)) {
      logger.trace(LogMarker.SERIALIZER_VERBOSE, "Read Float {}", value);
    }
    return value;
  }

  /**
   * Writes an instance of <code>Double</code> to a <code>DataOutput</code>.
   *
   * @throws IOException A problem occurs while writing to <code>out</code>
   * @throws NullPointerException if value is null.
   *
   * @see #readDouble
   */
  public static void writeDouble(Double value, DataOutput out) throws IOException {

    InternalDataSerializer.checkOut(out);

    if (logger.isTraceEnabled(LogMarker.SERIALIZER_VERBOSE)) {
      logger.trace(LogMarker.SERIALIZER_VERBOSE, "Writing Double {}", value);
    }

    out.writeDouble(value);
  }

  /**
   * Reads an instance of <code>Double</code> from a <code>DataInput</code>.
   *
   * @throws IOException A problem occurs while reading from <code>in</code>
   */
  public static Double readDouble(DataInput in) throws IOException {
    InternalDataSerializer.checkIn(in);

    Double value = in.readDouble();
    if (logger.isTraceEnabled(LogMarker.SERIALIZER_VERBOSE)) {
      logger.trace(LogMarker.SERIALIZER_VERBOSE, "Read Double {}", value);
    }
    return value;
  }

  /**
   * Writes a primitive <code>boolean</code> to a <code>DataOutput</code>.
   *
   * @throws IOException A problem occurs while writing to <code>out</code>
   *
   * @see DataOutput#writeBoolean
   * @since GemFire 5.1
   */
  public static void writePrimitiveBoolean(boolean value, DataOutput out) throws IOException {

    InternalDataSerializer.checkOut(out);

    if (logger.isTraceEnabled(LogMarker.SERIALIZER_VERBOSE)) {
      logger.trace(LogMarker.SERIALIZER_VERBOSE, "Writing Boolean {}", value);
    }

    out.writeBoolean(value);
  }

  /**
   * Reads a primitive <code>boolean</code> from a <code>DataInput</code>.
   *
   * @throws IOException A problem occurs while reading from <code>in</code>
   * @see DataInput#readBoolean
   * @since GemFire 5.1
   */
  public static boolean readPrimitiveBoolean(DataInput in) throws IOException {
    InternalDataSerializer.checkIn(in);

    boolean value = in.readBoolean();
    if (logger.isTraceEnabled(LogMarker.SERIALIZER_VERBOSE)) {
      logger.trace(LogMarker.SERIALIZER_VERBOSE, "Read Boolean {}", value);
    }
    return value;
  }

  /**
   * Writes a primitive <code>byte</code> to a <code>DataOutput</code>.
   *
   * @throws IOException A problem occurs while writing to <code>out</code>
   *
   * @see DataOutput#writeByte
   * @since GemFire 5.1
   */
  public static void writePrimitiveByte(byte value, DataOutput out) throws IOException {

    InternalDataSerializer.checkOut(out);

    if (logger.isTraceEnabled(LogMarker.SERIALIZER_VERBOSE)) {
      logger.trace(LogMarker.SERIALIZER_VERBOSE, "Writing Byte {}", value);
    }

    out.writeByte(value);
  }

  /**
   * Reads a primitive <code>byte</code> from a <code>DataInput</code>.
   *
   * @throws IOException A problem occurs while reading from <code>in</code>
   * @see DataInput#readByte
   * @since GemFire 5.1
   */
  public static byte readPrimitiveByte(DataInput in) throws IOException {
    InternalDataSerializer.checkIn(in);

    byte value = in.readByte();
    if (logger.isTraceEnabled(LogMarker.SERIALIZER_VERBOSE)) {
      logger.trace(LogMarker.SERIALIZER_VERBOSE, "Read Byte {}", value);
    }
    return value;
  }

  /**
   * Writes a primitive <code>char</code> to a <code>DataOutput</code>.
   *
   * @throws IOException A problem occurs while writing to <code>out</code>
   *
   * @see DataOutput#writeChar
   * @since GemFire 5.1
   */
  public static void writePrimitiveChar(char value, DataOutput out) throws IOException {

    InternalDataSerializer.checkOut(out);

    if (logger.isTraceEnabled(LogMarker.SERIALIZER_VERBOSE)) {
      logger.trace(LogMarker.SERIALIZER_VERBOSE, "Writing Char {}", value);
    }

    out.writeChar(value);
  }

  /**
   * Reads a primitive <code>char</code> from a <code>DataInput</code>.
   *
   * @throws IOException A problem occurs while reading from <code>in</code>
   * @see DataInput#readChar
   * @since GemFire 5.1
   */
  public static char readPrimitiveChar(DataInput in) throws IOException {
    InternalDataSerializer.checkIn(in);

    char value = in.readChar();
    if (logger.isTraceEnabled(LogMarker.SERIALIZER_VERBOSE)) {
      logger.trace(LogMarker.SERIALIZER_VERBOSE, "Read Char {}", value);
    }
    return value;
  }

  /**
   * Writes a primitive <code>short</code> to a <code>DataOutput</code>.
   *
   * @throws IOException A problem occurs while writing to <code>out</code>
   *
   * @see DataOutput#writeShort
   * @since GemFire 5.1
   */
  public static void writePrimitiveShort(short value, DataOutput out) throws IOException {

    InternalDataSerializer.checkOut(out);

    if (logger.isTraceEnabled(LogMarker.SERIALIZER_VERBOSE)) {
      logger.trace(LogMarker.SERIALIZER_VERBOSE, "Writing Short {}", value);
    }

    out.writeShort(value);
  }

  /**
   * Reads a primitive <code>short</code> from a <code>DataInput</code>.
   *
   * @throws IOException A problem occurs while reading from <code>in</code>
   * @see DataInput#readShort
   * @since GemFire 5.1
   */
  public static short readPrimitiveShort(DataInput in) throws IOException {
    InternalDataSerializer.checkIn(in);

    short value = in.readShort();
    if (logger.isTraceEnabled(LogMarker.SERIALIZER_VERBOSE)) {
      logger.trace(LogMarker.SERIALIZER_VERBOSE, "Read Short {}", value);
    }
    return value;
  }

  /**
   * Writes a primitive <code>int</code> as an unsigned byte to a <code>DataOutput</code>.
   *
   * @throws IOException A problem occurs while writing to <code>out</code>
   *
   * @see DataOutput#writeByte
   * @see DataInput#readUnsignedByte
   * @since GemFire 5.1
   */
  public static void writeUnsignedByte(int value, DataOutput out) throws IOException {

    InternalDataSerializer.checkOut(out);

    if (logger.isTraceEnabled(LogMarker.SERIALIZER_VERBOSE)) {
      logger.trace(LogMarker.SERIALIZER_VERBOSE, "Writing Unsigned Byte {}", value);
    }

    out.writeByte(value);
  }

  /**
   * Reads a primitive <code>int</code> as an unsigned byte from a <code>DataInput</code> using
   * {@link DataInput#readUnsignedByte}.
   *
   * @throws IOException A problem occurs while reading from <code>in</code>
   * @since GemFire 5.1
   */
  public static int readUnsignedByte(DataInput in) throws IOException {
    InternalDataSerializer.checkIn(in);

    int value = in.readUnsignedByte();
    if (logger.isTraceEnabled(LogMarker.SERIALIZER_VERBOSE)) {
      logger.trace(LogMarker.SERIALIZER_VERBOSE, "Read Unsigned Byte {}", value);
    }
    return value;
  }

  /**
   * Writes a primitive <code>int</code> as an unsigned short to a <code>DataOutput</code>.
   *
   * @throws IOException A problem occurs while writing to <code>out</code>
   *
   * @see DataOutput#writeShort
   * @see DataInput#readUnsignedShort
   * @since GemFire 5.1
   */
  public static void writeUnsignedShort(int value, DataOutput out) throws IOException {

    InternalDataSerializer.checkOut(out);

    if (logger.isTraceEnabled(LogMarker.SERIALIZER_VERBOSE)) {
      logger.trace(LogMarker.SERIALIZER_VERBOSE, "Writing Unsigned Short {}", value);
    }

    out.writeShort(value);
  }

  /**
   * Reads a primitive <code>int</code> as an unsigned short from a <code>DataInput</code> using
   * {@link DataInput#readUnsignedShort}.
   *
   * @throws IOException A problem occurs while reading from <code>in</code>
   * @since GemFire 5.1
   */
  public static int readUnsignedShort(DataInput in) throws IOException {
    InternalDataSerializer.checkIn(in);

    int value = in.readUnsignedShort();
    if (logger.isTraceEnabled(LogMarker.SERIALIZER_VERBOSE)) {
      logger.trace(LogMarker.SERIALIZER_VERBOSE, "Read Unsigned Short {}", value);
    }
    return value;
  }

  /**
   * Writes a primitive <code>int</code> to a <code>DataOutput</code>.
   *
   * @throws IOException A problem occurs while writing to <code>out</code>
   *
   * @see DataOutput#writeInt
   */
  public static void writePrimitiveInt(int value, DataOutput out) throws IOException {

    InternalDataSerializer.checkOut(out);

    if (logger.isTraceEnabled(LogMarker.SERIALIZER_VERBOSE)) {
      logger.trace(LogMarker.SERIALIZER_VERBOSE, "Writing Integer {}", value);
    }

    out.writeInt(value);
  }

  /**
   * Reads a primitive <code>int</code> from a <code>DataInput</code>.
   *
   * @throws IOException A problem occurs while reading from <code>in</code>
   * @see DataInput#readInt
   * @since GemFire 5.1
   */
  public static int readPrimitiveInt(DataInput in) throws IOException {
    InternalDataSerializer.checkIn(in);

    int value = in.readInt();
    if (logger.isTraceEnabled(LogMarker.SERIALIZER_VERBOSE)) {
      logger.trace(LogMarker.SERIALIZER_VERBOSE, "Read Integer {}", value);
    }
    return value;
  }

  /**
   * Writes a primitive <code>long</code> to a <code>DataOutput</code>.
   *
   * @throws IOException A problem occurs while writing to <code>out</code>
   *
   * @see DataOutput#writeLong
   * @since GemFire 5.1
   */
  public static void writePrimitiveLong(long value, DataOutput out) throws IOException {

    InternalDataSerializer.checkOut(out);

    if (logger.isTraceEnabled(LogMarker.SERIALIZER_VERBOSE)) {
      logger.trace(LogMarker.SERIALIZER_VERBOSE, "Writing Long {}", value);
    }

    out.writeLong(value);
  }

  /**
   * Reads a primitive <code>long</code> from a <code>DataInput</code>.
   *
   * @throws IOException A problem occurs while reading from <code>in</code>
   * @see DataInput#readLong
   * @since GemFire 5.1
   */
  public static long readPrimitiveLong(DataInput in) throws IOException {
    InternalDataSerializer.checkIn(in);

    long value = in.readLong();
    if (logger.isTraceEnabled(LogMarker.SERIALIZER_VERBOSE)) {
      logger.trace(LogMarker.SERIALIZER_VERBOSE, "Read Long {}", value);
    }
    return value;
  }

  /**
   * Writes a primitive <code>float</code> to a <code>DataOutput</code>.
   *
   * @throws IOException A problem occurs while writing to <code>out</code>
   *
   * @see DataOutput#writeFloat
   * @since GemFire 5.1
   */
  public static void writePrimitiveFloat(float value, DataOutput out) throws IOException {

    InternalDataSerializer.checkOut(out);

    if (logger.isTraceEnabled(LogMarker.SERIALIZER_VERBOSE)) {
      logger.trace(LogMarker.SERIALIZER_VERBOSE, "Writing Float {}", value);
    }

    out.writeFloat(value);
  }

  /**
   * Reads a primitive <code>float</code> from a <code>DataInput</code>.
   *
   * @throws IOException A problem occurs while reading from <code>in</code>
   * @see DataInput#readFloat
   * @since GemFire 5.1
   */
  public static float readPrimitiveFloat(DataInput in) throws IOException {
    InternalDataSerializer.checkIn(in);

    float value = in.readFloat();
    if (logger.isTraceEnabled(LogMarker.SERIALIZER_VERBOSE)) {
      logger.trace(LogMarker.SERIALIZER_VERBOSE, "Read Float {}", value);
    }
    return value;
  }

  /**
   * Writes a primtive <code>double</code> to a <code>DataOutput</code>.
   *
   * @throws IOException A problem occurs while writing to <code>out</code>
   *
   * @see DataOutput#writeDouble
   * @since GemFire 5.1
   */
  public static void writePrimitiveDouble(double value, DataOutput out) throws IOException {

    InternalDataSerializer.checkOut(out);

    if (logger.isTraceEnabled(LogMarker.SERIALIZER_VERBOSE)) {
      logger.trace(LogMarker.SERIALIZER_VERBOSE, "Writing Double {}", value);
    }

    out.writeDouble(value);
  }

  /**
   * Reads a primitive <code>double</code> from a <code>DataInput</code>.
   *
   * @throws IOException A problem occurs while reading from <code>in</code>
   * @see DataInput#readDouble
   * @since GemFire 5.1
   */
  public static double readPrimitiveDouble(DataInput in) throws IOException {
    InternalDataSerializer.checkIn(in);

    double value = in.readDouble();
    if (logger.isTraceEnabled(LogMarker.SERIALIZER_VERBOSE)) {
      logger.trace(LogMarker.SERIALIZER_VERBOSE, "Read Double {}", value);
    }
    return value;
  }

  /**
   * Writes an array of <code>byte</code>s to a <code>DataOutput</code>. This method will serialize
   * a <code>null</code> array and not throw a <code>NullPointerException</code>.
   *
   * @throws IOException A problem occurs while writing to <code>out</code>
   *
   * @see #readByteArray
   */
  public static void writeByteArray(byte[] array, DataOutput out) throws IOException {
    int len = 0;
    if (array != null) {
      len = array.length;
    }
    writeByteArray(array, len, out);
  }

  /**
   * Writes the first <code>len</code> elements of an array of <code>byte</code>s to a
   * <code>DataOutput</code>. This method will serialize a <code>null</code> array and not throw a
   * <code>NullPointerException</code>.
   *
   * @param len the actual number of entries to write. If len is greater than then length of the
   *        array then the entire array is written.
   * @throws IOException A problem occurs while writing to <code>out</code>
   *
   * @see #readByteArray
   */
  public static void writeByteArray(byte[] array, int len, DataOutput out) throws IOException {

    InternalDataSerializer.checkOut(out);

    int length = len; // to avoid warnings about parameter assignment

    if (array == null) {
      length = -1;
    } else {
      if (length > array.length) {
        length = array.length;
      }
    }
    InternalDataSerializer.writeArrayLength(length, out);
    if (logger.isTraceEnabled(LogMarker.SERIALIZER_VERBOSE)) {
      logger.trace(LogMarker.SERIALIZER_VERBOSE, "Writing byte array of length {}", length);
    }
    if (length > 0) {
      out.write(array, 0, length);
    }
  }

  /**
   * Serialize the given object <code>obj</code> into a byte array using
   * {@link #writeObject(Object, DataOutput)} and then writes the byte array to the given data
   * output <code>out</code> in the same format {@link #writeByteArray(byte[], DataOutput)} does.
   * This method will serialize a <code>null</code> obj and not throw a
   * <code>NullPointerException</code>.
   *
   * @param obj the object to serialize and write
   * @param out the data output to write the byte array to
   * @throws IllegalArgumentException if a problem occurs while serialize <code>obj</code>
   * @throws IOException if a problem occurs while writing to <code>out</code>
   *
   * @see #readByteArray
   * @since GemFire 5.0.2
   */
  public static void writeObjectAsByteArray(Object obj, DataOutput out) throws IOException {
    Object object = obj;
    if (obj instanceof CachedDeserializable) {
      if (obj instanceof StoredObject) {
        StoredObject so = (StoredObject) obj;
        if (logger.isTraceEnabled(LogMarker.SERIALIZER_VERBOSE)) {
          logger.trace(LogMarker.SERIALIZER_VERBOSE, "writeObjectAsByteArray StoredObject");
        }
        so.sendAsByteArray(out);
        return;
      } else {
        object = ((CachedDeserializable) obj).getSerializedValue();
      }
    }
    if (logger.isTraceEnabled(LogMarker.SERIALIZER_VERBOSE)) {
      if (object == null) {
        logger.trace(LogMarker.SERIALIZER_VERBOSE, "writeObjectAsByteArray null");
      } else {
        logger.trace(LogMarker.SERIALIZER_VERBOSE, "writeObjectAsByteArray obj.getClass={}",
            object.getClass());
      }
    }
    if (object instanceof byte[] || object == null) {
      writeByteArray((byte[]) object, out);
    } else if (out instanceof ObjToByteArraySerializer) {
      ((ObjToByteArraySerializer) out).writeAsSerializedByteArray(object);
    } /*
       * else if (obj instanceof Sendable) { ((Sendable)obj).sendTo(out); }
       */
    else {
      HeapDataOutputStream hdos;
      if (object instanceof HeapDataOutputStream) {
        hdos = (HeapDataOutputStream) object;
      } else {
        Version v = StaticSerialization.getVersionForDataStreamOrNull(out);
        if (v == null) {
          v = Version.CURRENT;
        }
        hdos = new HeapDataOutputStream(v);
        try {
          DataSerializer.writeObject(object, hdos);
        } catch (IOException e) {
          throw new IllegalArgumentException("Problem while serializing.", e);
        }
      }
      InternalDataSerializer.writeArrayLength(hdos.size(), out);
      hdos.sendTo(out);
    }
  }

  /**
   * Reads an array of <code>byte</code>s from a <code>DataInput</code>.
   *
   * @throws IOException A problem occurs while reading from <code>in</code>
   *
   * @see #writeByteArray(byte[], DataOutput)
   */
  public static byte[] readByteArray(DataInput in) throws IOException {

    InternalDataSerializer.checkIn(in);

    byte[] result = StaticSerialization.readByteArray(in);

    if (logger.isTraceEnabled(LogMarker.SERIALIZER_VERBOSE)) {
      logger.trace(LogMarker.SERIALIZER_VERBOSE, "Read byte array of length {}",
          result == null ? "null" : result.length);
    }

    return result;
  }

  /**
   * Writes an array of <code>String</code>s to a <code>DataOutput</code>. This method will
   * serialize a <code>null</code> array and not throw a <code>NullPointerException</code>.
   *
   * @throws IOException A problem occurs while writing to <code>out</code>
   *
   * @see #readStringArray
   * @see #writeString
   */
  public static void writeStringArray(String[] array, DataOutput out) throws IOException {

    InternalDataSerializer.checkOut(out);


    StaticSerialization.writeStringArray(array, out);

    if (logger.isTraceEnabled(LogMarker.SERIALIZER_VERBOSE)) {
      int length;
      if (array == null) {
        length = -1;
      } else {
        length = array.length;
      }

      logger.trace(LogMarker.SERIALIZER_VERBOSE, "Writing String array of length {}", length);
    }
  }

  /**
   * Reads an array of <code>String</code>s from a <code>DataInput</code>.
   *
   * @throws IOException A problem occurs while reading from <code>in</code>
   *
   * @see #writeStringArray
   */
  public static String[] readStringArray(DataInput in) throws IOException {

    InternalDataSerializer.checkIn(in);

    String array[] = StaticSerialization.readStringArray(in);

    if (logger.isTraceEnabled(LogMarker.SERIALIZER_VERBOSE)) {
      logger.trace(LogMarker.SERIALIZER_VERBOSE, "Read String array of length {}",
          array == null ? "null" : array.length);
    }

    return array;
  }

  /**
   * Writes an array of <code>short</code>s to a <code>DataOutput</code>. This method will serialize
   * a <code>null</code> array and not throw a <code>NullPointerException</code>.
   *
   * @throws IOException A problem occurs while writing to <code>out</code>
   *
   * @see #readShortArray
   */
  public static void writeShortArray(short[] array, DataOutput out) throws IOException {

    InternalDataSerializer.checkOut(out);

    int length;
    if (array == null) {
      length = -1;
    } else {
      length = array.length;
    }
    InternalDataSerializer.writeArrayLength(length, out);
    if (logger.isTraceEnabled(LogMarker.SERIALIZER_VERBOSE)) {
      logger.trace(LogMarker.SERIALIZER_VERBOSE, "Writing short array of length {}", length);
    }
    if (length > 0) {
      for (int i = 0; i < length; i++) {
        out.writeShort(array[i]);
      }
    }
  }

  /**
   * Reads an array of <code>short</code>s from a <code>DataInput</code>.
   *
   * @throws IOException A problem occurs while reading from <code>in</code>
   *
   * @see #writeShortArray
   */
  public static short[] readShortArray(DataInput in) throws IOException {

    InternalDataSerializer.checkIn(in);

    int length = InternalDataSerializer.readArrayLength(in);
    if (length == -1) {
      return null;
    } else {
      short[] array = new short[length];
      for (int i = 0; i < length; i++) {
        array[i] = in.readShort();
      }

      if (logger.isTraceEnabled(LogMarker.SERIALIZER_VERBOSE)) {
        logger.trace(LogMarker.SERIALIZER_VERBOSE, "Read short array of length {}", length);
      }

      return array;
    }
  }

  /**
   * Writes an array of <code>char</code>s to a <code>DataOutput</code>.
   *
   * @throws IOException A problem occurs while writing to <code>out</code>
   *
   * @see #readCharArray
   * @since GemFire 5.7
   */
  public static void writeCharArray(char[] array, DataOutput out) throws IOException {
    InternalDataSerializer.writeCharArray(array, out);
  }

  /**
   * Reads an array of <code>char</code>s from a <code>DataInput</code>.
   *
   * @throws IOException A problem occurs while reading from <code>in</code>
   *
   * @see #writeCharArray
   * @since GemFire 5.7
   */
  public static char[] readCharArray(DataInput in) throws IOException {

    InternalDataSerializer.checkIn(in);

    int length = InternalDataSerializer.readArrayLength(in);
    if (length == -1) {
      return null;
    } else {
      char[] array = new char[length];
      for (int i = 0; i < length; i++) {
        array[i] = in.readChar();
      }

      if (logger.isTraceEnabled(LogMarker.SERIALIZER_VERBOSE)) {
        logger.trace(LogMarker.SERIALIZER_VERBOSE, "Read char array of length {}", length);
      }

      return array;
    }
  }

  /**
   * Writes an array of <code>boolean</code>s to a <code>DataOutput</code>.
   *
   * @throws IOException A problem occurs while writing to <code>out</code>
   *
   * @see #readBooleanArray
   * @since GemFire 5.7
   */
  public static void writeBooleanArray(boolean[] array, DataOutput out) throws IOException {

    InternalDataSerializer.checkOut(out);

    int length;
    if (array == null) {
      length = -1;
    } else {
      length = array.length;
    }
    InternalDataSerializer.writeArrayLength(length, out);
    if (logger.isTraceEnabled(LogMarker.SERIALIZER_VERBOSE)) {
      logger.trace(LogMarker.SERIALIZER_VERBOSE, "Writing boolean array of length {}", length);
    }
    if (length > 0) {
      for (int i = 0; i < length; i++) {
        out.writeBoolean(array[i]);
      }
    }
  }

  /**
   * Reads an array of <code>boolean</code>s from a <code>DataInput</code>.
   *
   * @throws IOException A problem occurs while reading from <code>in</code>
   *
   * @see #writeBooleanArray
   * @since GemFire 5.7
   */
  public static boolean[] readBooleanArray(DataInput in) throws IOException {

    InternalDataSerializer.checkIn(in);

    int length = InternalDataSerializer.readArrayLength(in);
    if (length == -1) {
      return null;
    } else {
      boolean[] array = new boolean[length];
      for (int i = 0; i < length; i++) {
        array[i] = in.readBoolean();
      }

      if (logger.isTraceEnabled(LogMarker.SERIALIZER_VERBOSE)) {
        logger.trace(LogMarker.SERIALIZER_VERBOSE, "Read boolean array of length {}", length);
      }

      return array;
    }
  }

  /**
   * Writes an <code>int</code> array to a <code>DataOutput</code>. This method will serialize a
   * <code>null</code> array and not throw a <code>NullPointerException</code>.
   *
   * @throws IOException A problem occurs while writing to <code>out</code>
   *
   * @see #readIntArray
   */
  public static void writeIntArray(int[] array, DataOutput out) throws IOException {

    InternalDataSerializer.checkOut(out);

    StaticSerialization.writeIntArray(array, out);

    if (logger.isTraceEnabled(LogMarker.SERIALIZER_VERBOSE)) {
      logger.trace(LogMarker.SERIALIZER_VERBOSE, "Writing int array of length {}",
          array == null ? "null" : array.length);
    }
  }

  /**
   * Reads an <code>int</code> array from a <code>DataInput</code>.
   *
   * @throws IOException A problem occurs while reading from <code>in</code>
   *
   * @see #writeIntArray
   */
  public static int[] readIntArray(DataInput in) throws IOException {

    InternalDataSerializer.checkIn(in);

    int[] result = StaticSerialization.readIntArray(in);

    if (logger.isTraceEnabled(LogMarker.SERIALIZER_VERBOSE)) {
      logger.trace(LogMarker.SERIALIZER_VERBOSE, "Read int array of length {}",
          result == null ? "null" : result.length);
    }

    return result;
  }

  /**
   * Writes an array of <code>long</code>s to a <code>DataOutput</code>. This method will serialize
   * a <code>null</code> array and not throw a <code>NullPointerException</code>.
   *
   * @throws IOException A problem occurs while writing to <code>out</code>
   *
   * @see #readLongArray
   */
  public static void writeLongArray(long[] array, DataOutput out) throws IOException {

    InternalDataSerializer.checkOut(out);

    int length;
    if (array == null) {
      length = -1;
    } else {
      length = array.length;
    }
    InternalDataSerializer.writeArrayLength(length, out);
    if (logger.isTraceEnabled(LogMarker.SERIALIZER_VERBOSE)) {
      logger.trace(LogMarker.SERIALIZER_VERBOSE, "Writing long array of length {}", length);
    }
    if (length > 0) {
      for (int i = 0; i < length; i++) {
        out.writeLong(array[i]);
      }
    }
  }

  /**
   * Reads an array of <code>long</code>s from a <code>DataInput</code>.
   *
   * @throws IOException A problem occurs while reading from <code>in</code>
   *
   * @see #writeLongArray
   */
  public static long[] readLongArray(DataInput in) throws IOException {

    InternalDataSerializer.checkIn(in);

    int length = InternalDataSerializer.readArrayLength(in);
    if (length == -1) {
      return null;
    } else {
      long[] array = new long[length];
      for (int i = 0; i < length; i++) {
        array[i] = in.readLong();
      }

      if (logger.isTraceEnabled(LogMarker.SERIALIZER_VERBOSE)) {
        logger.trace(LogMarker.SERIALIZER_VERBOSE, "Read long array of length {}", length);
      }

      return array;
    }
  }

  /**
   * Writes an array of <code>float</code>s to a <code>DataOutput</code>. This method will serialize
   * a <code>null</code> array and not throw a <code>NullPointerException</code>.
   *
   * @throws IOException A problem occurs while writing to <code>out</code>
   *
   * @see #readFloatArray
   */
  public static void writeFloatArray(float[] array, DataOutput out) throws IOException {

    InternalDataSerializer.checkOut(out);

    int length;
    if (array == null) {
      length = -1;
    } else {
      length = array.length;
    }
    InternalDataSerializer.writeArrayLength(length, out);
    if (logger.isTraceEnabled(LogMarker.SERIALIZER_VERBOSE)) {
      logger.trace(LogMarker.SERIALIZER_VERBOSE, "Writing float array of length {}", length);
    }
    if (length > 0) {
      for (int i = 0; i < length; i++) {
        out.writeFloat(array[i]);
      }
    }
  }

  /**
   * Reads an array of <code>float</code>s from a <code>DataInput</code>.
   *
   * @throws IOException A problem occurs while reading from <code>in</code>
   *
   * @see #writeFloatArray
   */
  public static float[] readFloatArray(DataInput in) throws IOException {

    InternalDataSerializer.checkIn(in);

    int length = InternalDataSerializer.readArrayLength(in);
    if (length == -1) {
      return null;
    } else {
      float[] array = new float[length];
      for (int i = 0; i < length; i++) {
        array[i] = in.readFloat();
      }

      if (logger.isTraceEnabled(LogMarker.SERIALIZER_VERBOSE)) {
        logger.trace(LogMarker.SERIALIZER_VERBOSE, "Read float array of length {}", length);
      }

      return array;
    }
  }

  /**
   * Writes an array of <code>double</code>s to a <code>DataOutput</code>. This method will
   * serialize a <code>null</code> array and not throw a <code>NullPointerException</code>.
   *
   * @throws IOException A problem occurs while writing to <code>out</code>
   *
   * @see #readDoubleArray
   */
  public static void writeDoubleArray(double[] array, DataOutput out) throws IOException {

    InternalDataSerializer.checkOut(out);

    int length;
    if (array == null) {
      length = -1;
    } else {
      length = array.length;
    }
    InternalDataSerializer.writeArrayLength(length, out);
    if (logger.isTraceEnabled(LogMarker.SERIALIZER_VERBOSE)) {
      logger.trace(LogMarker.SERIALIZER_VERBOSE, "Writing double array of length {}", length);
    }
    if (length > 0) {
      for (int i = 0; i < length; i++) {
        out.writeDouble(array[i]);
      }
    }
  }

  /**
   * Reads an array of <code>double</code>s from a <code>DataInput</code>.
   *
   * @throws IOException A problem occurs while reading from <code>in</code>
   *
   * @see #writeDoubleArray
   */
  public static double[] readDoubleArray(DataInput in) throws IOException {

    InternalDataSerializer.checkIn(in);

    int length = InternalDataSerializer.readArrayLength(in);
    if (length == -1) {
      return null;
    } else {
      double[] array = new double[length];
      for (int i = 0; i < length; i++) {
        array[i] = in.readDouble();
      }

      if (logger.isTraceEnabled(LogMarker.SERIALIZER_VERBOSE)) {
        logger.trace(LogMarker.SERIALIZER_VERBOSE, "Read double array of length {}", length);
      }

      return array;
    }
  }

  /**
   * Writes an array of <code>Object</code>s to a <code>DataOutput</code>. This method will
   * serialize a <code>null</code> array and not throw a <code>NullPointerException</code>.
   *
   * @throws IOException A problem occurs while writing to <code>out</code>
   *
   * @see #readObjectArray
   * @see #writeObject(Object, DataOutput)
   */
  public static void writeObjectArray(Object[] array, DataOutput out) throws IOException {
    InternalDataSerializer.writeObjectArray(array, out, false);
  }

  /**
   * Reads an array of <code>Object</code>s from a <code>DataInput</code>.
   *
   * @throws IOException A problem occurs while reading from <code>in</code>
   *
   * @see #writeObjectArray
   * @see #readObject
   */
  public static Object[] readObjectArray(DataInput in) throws IOException, ClassNotFoundException {

    InternalDataSerializer.checkIn(in);

    int length = InternalDataSerializer.readArrayLength(in);
    if (length == -1) {
      return null;
    } else {
      Class<?> c;
      byte typeCode = in.readByte();
      String typeString = null;
      if (typeCode == DSCODE.CLASS.toByte()) {
        typeString = readString(in);
      }

      InternalCache cache = GemFireCacheImpl.getInstance();
      boolean lookForPdxInstance = false;
      ClassNotFoundException cnfEx = null;
      if (typeCode == DSCODE.CLASS.toByte() && cache != null
          && cache.getPdxReadSerializedByAnyGemFireServices()) {
        try {
          c = InternalDataSerializer.getCachedClass(typeString);
          lookForPdxInstance = true;
        } catch (ClassNotFoundException e) {
          c = Object.class;
          cnfEx = e;
        }
      } else {
        if (typeCode == DSCODE.CLASS.toByte()) {
          c = InternalDataSerializer.getCachedClass(typeString);
        } else {
          c = StaticSerialization.decodePrimitiveClass(typeCode);
        }
      }
      Object o = null;
      if (length > 0) {
        o = readObject(in);
        if (lookForPdxInstance && o instanceof PdxInstance) {
          lookForPdxInstance = false;
          c = Object.class;
        }
      }
      Object[] array = (Object[]) Array.newInstance(c, length);
      if (length > 0) {
        array[0] = o;
      }
      for (int i = 1; i < length; i++) {
        o = readObject(in);
        if (lookForPdxInstance && o instanceof PdxInstance) {
          // create an Object[] and copy all the entries we already did into it
          lookForPdxInstance = false;
          c = Object.class;
          Object[] newArray = (Object[]) Array.newInstance(c, length);
          System.arraycopy(array, 0, newArray, 0, i);
          array = newArray;
        }
        array[i] = o;
      }
      if (lookForPdxInstance && cnfEx != null && length > 0) {
        // We have a non-empty array and didn't find any
        // PdxInstances in it. So we should have been able
        // to load the element type.
        // Note that empty arrays in this case will deserialize
        // as an Object[] since we can't tell if the element
        // type is a pdx one.
        throw cnfEx;
      }

      if (logger.isTraceEnabled(LogMarker.SERIALIZER_VERBOSE)) {
        logger.trace(LogMarker.SERIALIZER_VERBOSE, "Read Object array of length {}", length);
      }

      return array;
    }
  }

  /**
   * Writes an array of <tt>byte[]</tt> to a <tt>DataOutput</tt>.
   *
   * @throws IOException A problem occurs while writing to <tt>out</tt>.
   *
   */
  public static void writeArrayOfByteArrays(byte[][] array, DataOutput out) throws IOException {

    InternalDataSerializer.checkOut(out);
    int length;
    if (array == null) {
      length = -1;
    } else {
      length = array.length;
    }
    InternalDataSerializer.writeArrayLength(length, out);
    if (logger.isTraceEnabled(LogMarker.SERIALIZER_VERBOSE)) {
      logger.trace(LogMarker.SERIALIZER_VERBOSE, "Writing byte[][] of length {}", length);
    }
    if (length >= 0) {
      for (int i = 0; i < length; i++) {
        writeByteArray(array[i], out);
      }
    }
  }

  /**
   * Reads an array of <code>byte[]</code>s from a <code>DataInput</code>.
   *
   * @throws IOException A problem occurs while reading from <code>in</code>
   */
  public static byte[][] readArrayOfByteArrays(DataInput in)
      throws IOException, ClassNotFoundException {

    InternalDataSerializer.checkIn(in);

    int length = InternalDataSerializer.readArrayLength(in);
    if (length == -1) {
      return null;
    } else {
      byte[][] array = new byte[length][];
      for (int i = 0; i < length; i++) {
        array[i] = readByteArray(in);
      }

      if (logger.isTraceEnabled(LogMarker.SERIALIZER_VERBOSE)) {
        logger.trace(LogMarker.SERIALIZER_VERBOSE, "Read byte[][] of length {}", length);
      }

      return array;
    }
  }


  /**
   * Writes an <code>ArrayList</code> to a <code>DataOutput</code>. Note that even though
   * <code>list</code> may be an instance of a subclass of <code>ArrayList</code>,
   * <code>readArrayList</code> will always return an instance of <code>ArrayList</code>, <B>not</B>
   * an instance of the subclass. To preserve the class type of <code>list</code>,
   * {@link #writeObject(Object, DataOutput)} should be used for data serialization. This method
   * will serialize a <code>null</code> list and not throw a <code>NullPointerException</code>.
   *
   * @throws IOException A problem occurs while writing to <code>out</code>
   *
   * @see #readArrayList
   */
  public static void writeArrayList(ArrayList<?> list, DataOutput out) throws IOException {

    InternalDataSerializer.checkOut(out);

    int size;
    if (list == null) {
      size = -1;
    } else {
      size = list.size();
    }
    InternalDataSerializer.writeArrayLength(size, out);
    if (logger.isTraceEnabled(LogMarker.SERIALIZER_VERBOSE)) {
      logger.trace(LogMarker.SERIALIZER_VERBOSE, "Writing ArrayList with {} elements: {}", size,
          list);
    }
    if (size > 0) {
      for (int i = 0; i < size; i++) {
        writeObject(list.get(i), out);
      }
    }
  }



  /**
   * Reads an <code>ArrayList</code> from a <code>DataInput</code>.
   *
   * @throws IOException A problem occurs while reading from <code>in</code>
   * @throws ClassNotFoundException The class of one of the <Code>ArrayList</code>'s elements cannot
   *         be found.
   *
   * @see #writeArrayList
   */
  public static <E> ArrayList<E> readArrayList(DataInput in)
      throws IOException, ClassNotFoundException {

    InternalDataSerializer.checkIn(in);

    int size = InternalDataSerializer.readArrayLength(in);
    if (size == -1) {
      return null;
    } else {
      ArrayList<E> list = new ArrayList<>(size);
      for (int i = 0; i < size; i++) {
        E element = DataSerializer.readObject(in);
        list.add(element);
      }

      if (logger.isTraceEnabled(LogMarker.SERIALIZER_VERBOSE)) {
        logger.trace(LogMarker.SERIALIZER_VERBOSE, "Read ArrayList with {} elements: {}", size,
            list);
      }

      return list;
    }
  }

  /**
   * Writes an <code>Vector</code> to a <code>DataOutput</code>. Note that even though
   * <code>list</code> may be an instance of a subclass of <code>Vector</code>,
   * <code>readVector</code> will always return an instance of <code>Vector</code>, <B>not</B> an
   * instance of the subclass. To preserve the class type of <code>list</code>,
   * {@link #writeObject(Object, DataOutput)} should be used for data serialization.
   *
   * @throws IOException A problem occurs while writing to <code>out</code>
   *
   * @see #readVector
   * @since GemFire 5.7
   */
  public static void writeVector(Vector<?> list, DataOutput out) throws IOException {

    InternalDataSerializer.checkOut(out);

    int size;
    if (list == null) {
      size = -1;
    } else {
      size = list.size();
    }
    InternalDataSerializer.writeArrayLength(size, out);
    if (logger.isTraceEnabled(LogMarker.SERIALIZER_VERBOSE)) {
      logger.trace(LogMarker.SERIALIZER_VERBOSE, "Writing Vector with {} elements: {}", size, list);
    }
    if (size > 0) {
      for (int i = 0; i < size; i++) {
        writeObject(list.get(i), out);
      }
    }
  }

  /**
   * Reads an <code>Vector</code> from a <code>DataInput</code>.
   *
   * @throws IOException A problem occurs while reading from <code>in</code>
   * @throws ClassNotFoundException The class of one of the <Code>Vector</code>'s elements cannot be
   *         found.
   *
   * @see #writeVector
   * @since GemFire 5.7
   */
  public static <E> Vector<E> readVector(DataInput in) throws IOException, ClassNotFoundException {

    InternalDataSerializer.checkIn(in);

    int size = InternalDataSerializer.readArrayLength(in);
    if (size == -1) {
      return null;
    } else {
      Vector<E> list = new Vector<>(size);
      for (int i = 0; i < size; i++) {
        E element = DataSerializer.readObject(in);
        list.add(element);
      }

      if (logger.isTraceEnabled(LogMarker.SERIALIZER_VERBOSE)) {
        logger.trace(LogMarker.SERIALIZER_VERBOSE, "Read Vector with {} elements: {}", size, list);
      }

      return list;
    }
  }

  /**
   * Writes an <code>Stack</code> to a <code>DataOutput</code>. Note that even though
   * <code>list</code> may be an instance of a subclass of <code>Stack</code>,
   * <code>readStack</code> will always return an instance of <code>Stack</code>, <B>not</B> an
   * instance of the subclass. To preserve the class type of <code>list</code>,
   * {@link #writeObject(Object, DataOutput)} should be used for data serialization.
   *
   * @throws IOException A problem occurs while writing to <code>out</code>
   *
   * @see #readStack
   * @since GemFire 5.7
   */
  public static void writeStack(Stack<?> list, DataOutput out) throws IOException {

    InternalDataSerializer.checkOut(out);

    int size;
    if (list == null) {
      size = -1;
    } else {
      size = list.size();
    }
    InternalDataSerializer.writeArrayLength(size, out);
    if (logger.isTraceEnabled(LogMarker.SERIALIZER_VERBOSE)) {
      logger.trace(LogMarker.SERIALIZER_VERBOSE, "Writing Stack with {} elements: {}", size, list);
    }
    if (size > 0) {
      for (int i = 0; i < size; i++) {
        writeObject(list.get(i), out);
      }
    }
  }

  /**
   * Reads an <code>Stack</code> from a <code>DataInput</code>.
   *
   * @throws IOException A problem occurs while reading from <code>in</code>
   * @throws ClassNotFoundException The class of one of the <Code>Stack</code>'s elements cannot be
   *         found.
   *
   * @see #writeStack
   * @since GemFire 5.7
   */
  public static <E> Stack<E> readStack(DataInput in) throws IOException, ClassNotFoundException {

    InternalDataSerializer.checkIn(in);

    int size = InternalDataSerializer.readArrayLength(in);
    if (size == -1) {
      return null;
    } else {
      Stack<E> list = new Stack<>();
      for (int i = 0; i < size; i++) {
        E element = DataSerializer.readObject(in);
        list.add(element);
      }

      if (logger.isTraceEnabled(LogMarker.SERIALIZER_VERBOSE)) {
        logger.trace(LogMarker.SERIALIZER_VERBOSE, "Read Stack with {} elements: {}", size, list);
      }

      return list;
    }
  }

  /**
   * Writes an <code>LinkedList</code> to a <code>DataOutput</code>. Note that even though
   * <code>list</code> may be an instance of a subclass of <code>LinkedList</code>,
   * <code>readLinkedList</code> will always return an instance of <code>LinkedList</code>,
   * <B>not</B> an instance of the subclass. To preserve the class type of <code>list</code>,
   * {@link #writeObject(Object, DataOutput)} should be used for data serialization. This method
   * will serialize a <code>null</code> list and not throw a <code>NullPointerException</code>.
   *
   * @throws IOException A problem occurs while writing to <code>out</code>
   *
   * @see #readLinkedList
   */
  public static void writeLinkedList(LinkedList<?> list, DataOutput out) throws IOException {

    InternalDataSerializer.checkOut(out);

    int size;
    if (list == null) {
      size = -1;
    } else {
      size = list.size();
    }
    InternalDataSerializer.writeArrayLength(size, out);
    if (logger.isTraceEnabled(LogMarker.SERIALIZER_VERBOSE)) {
      logger.trace(LogMarker.SERIALIZER_VERBOSE, "Writing LinkedList with {} elements: {}", size,
          list);
    }
    if (size > 0) {
      for (Object e : list) {
        writeObject(e, out);
      }
    }
  }

  /**
   * Reads an <code>LinkedList</code> from a <code>DataInput</code>.
   *
   * @throws IOException A problem occurs while reading from <code>in</code>
   * @throws ClassNotFoundException The class of one of the <Code>LinkedList</code>'s elements
   *         cannot be found.
   *
   * @see #writeLinkedList
   */
  public static <E> LinkedList<E> readLinkedList(DataInput in)
      throws IOException, ClassNotFoundException {

    InternalDataSerializer.checkIn(in);

    int size = InternalDataSerializer.readArrayLength(in);
    if (size == -1) {
      return null;
    } else {
      LinkedList<E> list = new LinkedList<>();
      for (int i = 0; i < size; i++) {
        E element = DataSerializer.readObject(in);
        list.add(element);
      }

      if (logger.isTraceEnabled(LogMarker.SERIALIZER_VERBOSE)) {
        logger.trace(LogMarker.SERIALIZER_VERBOSE, "Read LinkedList with {} elements: {}", size,
            list);
      }

      return list;
    }
  }

  /**
   * Writes a <code>HashSet</code> to a <code>DataOutput</code>. Note that even though
   * <code>set</code> may be an instance of a subclass of <code>HashSet</code>,
   * <code>readHashSet</code> will always return an instance of <code>HashSet</code>, <B>not</B> an
   * instance of the subclass. To preserve the class type of <code>set</code>,
   * {@link #writeObject(Object, DataOutput)} should be used for data serialization. This method
   * will serialize a <code>null</code> set and not throw a <code>NullPointerException</code>.
   *
   * @throws IOException A problem occurs while writing to <code>out</code>
   *
   * @see #readHashSet
   */
  public static void writeHashSet(HashSet<?> set, DataOutput out) throws IOException {
    InternalDataSerializer.writeSet(set, out);
  }

  /**
   * Reads a <code>HashSet</code> from a <code>DataInput</code>.
   *
   * @throws IOException A problem occurs while reading from <code>in</code>
   * @throws ClassNotFoundException The class of one of the <Code>HashSet</code>'s elements cannot
   *         be found.
   *
   * @see #writeHashSet
   */
  public static <E> HashSet<E> readHashSet(DataInput in)
      throws IOException, ClassNotFoundException {

    InternalDataSerializer.checkIn(in);

    int size = InternalDataSerializer.readArrayLength(in);
    if (size == -1) {
      return null;
    } else {
      HashSet<E> set = new HashSet<>(size);
      for (int i = 0; i < size; i++) {
        E element = DataSerializer.readObject(in);
        set.add(element);
      }

      if (logger.isTraceEnabled(LogMarker.SERIALIZER_VERBOSE)) {
        logger.trace(LogMarker.SERIALIZER_VERBOSE, "Read HashSet with {} elements: {}", size, set);
      }

      return set;
    }
  }

  /**
   * Writes a <code>LinkedHashSet</code> to a <code>DataOutput</code>. Note that even though
   * <code>set</code> may be an instance of a subclass of <code>LinkedHashSet</code>,
   * <code>readLinkedHashSet</code> will always return an instance of <code>LinkedHashSet</code>,
   * <B>not</B> an instance of the subclass. To preserve the class type of <code>set</code>,
   * {@link #writeObject(Object, DataOutput)} should be used for data serialization.
   *
   * @throws IOException A problem occurs while writing to <code>out</code>
   *
   * @see #readLinkedHashSet
   * @since GemFire 5.7
   */
  public static void writeLinkedHashSet(LinkedHashSet<?> set, DataOutput out) throws IOException {
    InternalDataSerializer.writeSet(set, out);
  }

  /**
   * Reads a <code>LinkedHashSet</code> from a <code>DataInput</code>.
   *
   * @throws IOException A problem occurs while reading from <code>in</code>
   * @throws ClassNotFoundException The class of one of the <Code>LinkedHashSet</code>'s elements
   *         cannot be found.
   *
   * @see #writeLinkedHashSet
   * @since GemFire 5.7
   */
  public static <E> LinkedHashSet<E> readLinkedHashSet(DataInput in)
      throws IOException, ClassNotFoundException {

    InternalDataSerializer.checkIn(in);

    int size = InternalDataSerializer.readArrayLength(in);
    if (size == -1) {
      return null;
    } else {
      LinkedHashSet<E> set = new LinkedHashSet<>(size);
      for (int i = 0; i < size; i++) {
        E element = DataSerializer.readObject(in);
        set.add(element);
      }

      if (logger.isTraceEnabled(LogMarker.SERIALIZER_VERBOSE)) {
        logger.trace(LogMarker.SERIALIZER_VERBOSE, "Read LinkedHashSet with {} elements: {}", size,
            set);
      }

      return set;
    }
  }

  /**
   * Writes a <code>HashMap</code> to a <code>DataOutput</code>. Note that even though
   * <code>map</code> may be an instance of a subclass of <code>HashMap</code>,
   * <code>readHashMap</code> will always return an instance of <code>HashMap</code>, <B>not</B> an
   * instance of the subclass. To preserve the class type of <code>map</code>,
   * {@link #writeObject(Object, DataOutput)} should be used for data serialization. This method
   * will serialize a <code>null</code> map and not throw a <code>NullPointerException</code>.
   *
   * @throws IOException A problem occurs while writing to <code>out</code>
   *
   * @see #readHashMap
   */
  public static void writeHashMap(Map<?, ?> map, DataOutput out) throws IOException {

    InternalDataSerializer.checkOut(out);

    int size;
    if (map == null) {
      size = -1;
    } else {
      size = map.size();
    }
    InternalDataSerializer.writeArrayLength(size, out);
    if (logger.isTraceEnabled(LogMarker.SERIALIZER_VERBOSE)) {
      logger.trace(LogMarker.SERIALIZER_VERBOSE, "Writing HashMap with {} elements: {}", size, map);
    }
    if (size > 0) {
      for (Map.Entry<?, ?> entry : map.entrySet()) {
        writeObject(entry.getKey(), out);
        writeObject(entry.getValue(), out);
      }
    }
  }

  /**
   * Reads a <code>HashMap</code> from a <code>DataInput</code>.
   *
   * @throws IOException A problem occurs while reading from <code>in</code>
   * @throws ClassNotFoundException The class of one of the <Code>HashMap</code>'s elements cannot
   *         be found.
   *
   * @see #writeHashMap
   */
  public static <K, V> HashMap<K, V> readHashMap(DataInput in)
      throws IOException, ClassNotFoundException {

    InternalDataSerializer.checkIn(in);

    int size = InternalDataSerializer.readArrayLength(in);
    if (size == -1) {
      return null;
    } else {
      HashMap<K, V> map = new HashMap<>(size);
      for (int i = 0; i < size; i++) {
        K key = DataSerializer.readObject(in);
        V value = DataSerializer.readObject(in);
        map.put(key, value);
      }

      if (logger.isTraceEnabled(LogMarker.SERIALIZER_VERBOSE)) {
        logger.trace(LogMarker.SERIALIZER_VERBOSE, "Read HashMap with {} elements: {}", size, map);
      }

      return map;
    }
  }

  /**
   * Writes a <code>IdentityHashMap</code> to a <code>DataOutput</code>. Note that even though
   * <code>map</code> may be an instance of a subclass of <code>IdentityHashMap</code>,
   * <code>readIdentityHashMap</code> will always return an instance of
   * <code>IdentityHashMap</code>, <B>not</B> an instance of the subclass. To preserve the class
   * type of <code>map</code>, {@link #writeObject(Object, DataOutput)} should be used for data
   * serialization.
   *
   * @throws IOException A problem occurs while writing to <code>out</code>
   *
   * @see #readIdentityHashMap
   */
  public static void writeIdentityHashMap(IdentityHashMap<?, ?> map, DataOutput out)
      throws IOException {

    InternalDataSerializer.checkOut(out);

    int size;
    if (map == null) {
      size = -1;
    } else {
      size = map.size();
    }
    InternalDataSerializer.writeArrayLength(size, out);
    if (logger.isTraceEnabled(LogMarker.SERIALIZER_VERBOSE)) {
      logger.trace(LogMarker.SERIALIZER_VERBOSE, "Writing IdentityHashMap with {} elements: {}",
          size, map);
    }
    if (size > 0) {
      for (Map.Entry<?, ?> entry : map.entrySet()) {
        writeObject(entry.getKey(), out);
        writeObject(entry.getValue(), out);
      }
    }
  }

  /**
   * Reads a <code>IdentityHashMap</code> from a <code>DataInput</code>. Note that key identity is
   * not preserved unless the keys belong to a class whose serialization preserves identity.
   *
   * @throws IOException A problem occurs while reading from <code>in</code>
   * @throws ClassNotFoundException The class of one of the <Code>IdentityHashMap</code>'s elements
   *         cannot be found.
   *
   * @see #writeIdentityHashMap
   */
  public static <K, V> IdentityHashMap<K, V> readIdentityHashMap(DataInput in)
      throws IOException, ClassNotFoundException {

    InternalDataSerializer.checkIn(in);

    int size = InternalDataSerializer.readArrayLength(in);
    if (size == -1) {
      return null;
    } else {
      IdentityHashMap<K, V> map = new IdentityHashMap<>(size);
      for (int i = 0; i < size; i++) {
        K key = DataSerializer.readObject(in);
        V value = DataSerializer.readObject(in);
        map.put(key, value);
      }

      if (logger.isTraceEnabled(LogMarker.SERIALIZER_VERBOSE)) {
        logger.trace(LogMarker.SERIALIZER_VERBOSE, "Read IdentityHashMap with {} elements: {}",
            size, map);
      }

      return map;
    }
  }

  /**
   * Writes a <code>ConcurrentHashMap</code> to a <code>DataOutput</code>. Note that even though
   * <code>map</code> may be an instance of a subclass of <code>ConcurrentHashMap</code>,
   * <code>readConcurrentHashMap</code> will always return an instance of
   * <code>ConcurrentHashMap</code>, <B>not</B> an instance of the subclass. To preserve the class
   * type of <code>map</code>, {@link #writeObject(Object, DataOutput)} should be used for data
   * serialization.
   * <P>
   * At this time if {@link #writeObject(Object, DataOutput)} is called with an instance of
   * ConcurrentHashMap then it will be serialized with normal java.io Serialization. So if you want
   * the keys and values of a ConcurrentHashMap to take advantage of GemFire serialization it must
   * be serialized with this method.
   *
   * @throws IOException A problem occurs while writing to <code>out</code>
   *
   * @see #readConcurrentHashMap
   * @since GemFire 6.6
   */
  public static void writeConcurrentHashMap(ConcurrentHashMap<?, ?> map, DataOutput out)
      throws IOException {

    InternalDataSerializer.checkOut(out);

    int size;
    Collection<Map.Entry<?, ?>> entrySnapshot = null;
    if (map == null) {
      size = -1;
    } else {
      entrySnapshot = new ArrayList<>(map.entrySet());
      size = entrySnapshot.size();
    }
    InternalDataSerializer.writeArrayLength(size, out);
    if (logger.isTraceEnabled(LogMarker.SERIALIZER_VERBOSE)) {
      logger.trace(LogMarker.SERIALIZER_VERBOSE, "Writing ConcurrentHashMap with {} elements: {}",
          size, entrySnapshot);
    }
    if (size > 0) {
      for (Map.Entry<?, ?> entry : entrySnapshot) {
        writeObject(entry.getKey(), out);
        writeObject(entry.getValue(), out);
      }
    }
  }

  /**
   * Reads a <code>ConcurrentHashMap</code> from a <code>DataInput</code>.
   *
   * @throws IOException A problem occurs while reading from <code>in</code>
   * @throws ClassNotFoundException The class of one of the <Code>ConcurrentHashMap</code>'s
   *         elements cannot be found.
   *
   * @see #writeConcurrentHashMap
   * @since GemFire 6.6
   */
  public static <K, V> ConcurrentHashMap<K, V> readConcurrentHashMap(DataInput in)
      throws IOException, ClassNotFoundException {

    InternalDataSerializer.checkIn(in);

    int size = InternalDataSerializer.readArrayLength(in);
    if (size == -1) {
      return null;
    } else {
      ConcurrentHashMap<K, V> map = new ConcurrentHashMap<>(size);
      for (int i = 0; i < size; i++) {
        K key = DataSerializer.readObject(in);
        V value = DataSerializer.readObject(in);
        map.put(key, value);
      }

      if (logger.isTraceEnabled(LogMarker.SERIALIZER_VERBOSE)) {
        logger.trace(LogMarker.SERIALIZER_VERBOSE, "Read ConcurrentHashMap with {} elements: {}",
            size, map);
      }

      return map;
    }
  }

  /**
   * Writes a <code>Hashtable</code> to a <code>DataOutput</code>. Note that even though
   * <code>map</code> may be an instance of a subclass of <code>Hashtable</code>,
   * <code>readHashtable</code> will always return an instance of <code>Hashtable</code>, <B>not</B>
   * an instance of the subclass. To preserve the class type of <code>map</code>,
   * {@link #writeObject(Object, DataOutput)} should be used for data serialization.
   *
   * @throws IOException A problem occurs while writing to <code>out</code>
   *
   * @see #readHashtable
   * @since GemFire 5.7
   */
  public static void writeHashtable(Hashtable<?, ?> map, DataOutput out) throws IOException {

    InternalDataSerializer.checkOut(out);

    int size;
    if (map == null) {
      size = -1;
    } else {
      size = map.size();
    }
    InternalDataSerializer.writeArrayLength(size, out);
    if (logger.isTraceEnabled(LogMarker.SERIALIZER_VERBOSE)) {
      logger.trace(LogMarker.SERIALIZER_VERBOSE, "Writing Hashtable with {} elements: {}", size,
          map);
    }
    if (size > 0) {
      for (Map.Entry<?, ?> entry : map.entrySet()) {
        writeObject(entry.getKey(), out);
        writeObject(entry.getValue(), out);
      }
    }
  }

  /**
   * Reads a <code>Hashtable</code> from a <code>DataInput</code>.
   *
   * @throws IOException A problem occurs while reading from <code>in</code>
   * @throws ClassNotFoundException The class of one of the <Code>Hashtable</code>'s elements cannot
   *         be found.
   *
   * @see #writeHashtable
   * @since GemFire 5.7
   */
  public static <K, V> Hashtable<K, V> readHashtable(DataInput in)
      throws IOException, ClassNotFoundException {

    InternalDataSerializer.checkIn(in);

    int size = InternalDataSerializer.readArrayLength(in);
    if (size == -1) {
      return null;
    } else {
      Hashtable<K, V> map = new Hashtable<>(size);
      for (int i = 0; i < size; i++) {
        K key = DataSerializer.readObject(in);
        V value = DataSerializer.readObject(in);
        map.put(key, value);
      }

      if (logger.isTraceEnabled(LogMarker.SERIALIZER_VERBOSE)) {
        logger.trace(LogMarker.SERIALIZER_VERBOSE, "Read Hashtable with {} elements: {}", size,
            map);
      }

      return map;
    }
  }

  /**
   * Writes a <code>TreeMap</code> to a <code>DataOutput</code>. Note that even though
   * <code>map</code> may be an instance of a subclass of <code>TreeMap</code>,
   * <code>readTreeMap</code> will always return an instance of <code>TreeMap</code>, <B>not</B> an
   * instance of the subclass. To preserve the class type of <code>map</code>,
   * {@link #writeObject(Object, DataOutput)} should be used for data serialization.
   * <p>
   * If the map has a comparator then it must also be serializable.
   *
   * @throws IOException A problem occurs while writing to <code>out</code>
   *
   * @see #readTreeMap
   * @since GemFire 5.7
   */
  public static void writeTreeMap(TreeMap<?, ?> map, DataOutput out) throws IOException {

    InternalDataSerializer.checkOut(out);

    int size;
    if (map == null) {
      size = -1;
    } else {
      size = map.size();
    }
    InternalDataSerializer.writeArrayLength(size, out);
    if (logger.isTraceEnabled(LogMarker.SERIALIZER_VERBOSE)) {
      logger.trace(LogMarker.SERIALIZER_VERBOSE, "Writing TreeMap with {} elements: {}", size, map);
    }
    if (size >= 0) {
      writeObject(map.comparator(), out);
      for (Map.Entry<?, ?> entry : map.entrySet()) {
        writeObject(entry.getKey(), out);
        writeObject(entry.getValue(), out);
      }
    }
  }

  /**
   * Reads a <code>TreeMap</code> from a <code>DataInput</code>.
   *
   * @throws IOException A problem occurs while reading from <code>in</code>
   * @throws ClassNotFoundException The class of one of the <Code>TreeMap</code>'s elements cannot
   *         be found.
   *
   * @see #writeTreeMap
   * @since GemFire 5.7
   */
  public static <K, V> TreeMap<K, V> readTreeMap(DataInput in)
      throws IOException, ClassNotFoundException {

    InternalDataSerializer.checkIn(in);

    int size = InternalDataSerializer.readArrayLength(in);
    if (size == -1) {
      return null;
    } else {
      Comparator<? super K> c = InternalDataSerializer.readNonPdxInstanceObject(in);
      TreeMap<K, V> map = new TreeMap<>(c);
      for (int i = 0; i < size; i++) {
        K key = DataSerializer.readObject(in);
        V value = DataSerializer.readObject(in);
        map.put(key, value);
      }

      if (logger.isTraceEnabled(LogMarker.SERIALIZER_VERBOSE)) {
        logger.trace(LogMarker.SERIALIZER_VERBOSE, "Read TreeMap with {} elements: {}", size, map);
      }

      return map;
    }
  }


  /**
   * Writes a <code>LinkedHashMap</code> to a <code>DataOutput</code>. Note that even though
   * <code>map</code> may be an instance of a subclass of <code>LinkedHashMap</code>,
   * <code>readLinkedHashMap</code> will always return an instance of <code>LinkedHashMap</code>,
   * <B>not</B> an instance of the subclass. To preserve the class type of <code>map</code>,
   * {@link #writeObject(Object, DataOutput)} should be used for data serialization. This method
   * will serialize a <code>null</code> map and not throw a <code>NullPointerException</code>.
   *
   * @throws IOException A problem occurs while writing to <code>out</code>
   * @see #readLinkedHashMap
   */
  public static void writeLinkedHashMap(Map<?, ?> map, DataOutput out) throws IOException {

    InternalDataSerializer.checkOut(out);

    int size;
    if (map == null) {
      size = -1;
    } else {
      size = map.size();
    }
    InternalDataSerializer.writeArrayLength(size, out);
    if (logger.isTraceEnabled(LogMarker.SERIALIZER_VERBOSE)) {
      logger.trace(LogMarker.SERIALIZER_VERBOSE, "Writing LinkedHashMap with {} elements: {}", size,
          map);
    }
    if (size > 0) {
      for (Map.Entry<?, ?> entry : map.entrySet()) {
        writeObject(entry.getKey(), out);
        writeObject(entry.getValue(), out);
      }
    }
  }

  /**
   * Reads a <code>LinkedHashMap</code> from a <code>DataInput</code>.
   *
   * @throws IOException A problem occurs while reading from <code>in</code>
   * @throws ClassNotFoundException The class of one of the <Code>HashMap</code>'s elements cannot
   *         be found.
   * @see #writeLinkedHashMap
   */
  public static <K, V> LinkedHashMap<K, V> readLinkedHashMap(DataInput in)
      throws IOException, ClassNotFoundException {

    InternalDataSerializer.checkIn(in);

    int size = InternalDataSerializer.readArrayLength(in);
    if (size == -1) {
      return null;
    } else {
      LinkedHashMap<K, V> map = new LinkedHashMap<>(size);
      for (int i = 0; i < size; i++) {
        K key = DataSerializer.readObject(in);
        V value = DataSerializer.readObject(in);
        map.put(key, value);
      }

      if (logger.isTraceEnabled(LogMarker.SERIALIZER_VERBOSE)) {
        logger.trace(LogMarker.SERIALIZER_VERBOSE, "Read LinkedHashMap with {} elements: {}", size,
            map);
      }

      return map;
    }
  }



  /**
   * Writes a <code>TreeSet</code> to a <code>DataOutput</code>. Note that even though
   * <code>set</code> may be an instance of a subclass of <code>TreeSet</code>,
   * <code>readTreeSet</code> will always return an instance of <code>TreeSet</code>, <B>not</B> an
   * instance of the subclass. To preserve the class type of <code>set</code>,
   * {@link #writeObject(Object, DataOutput)} should be used for data serialization.
   * <p>
   * If the set has a comparator then it must also be serializable.
   *
   * @throws IOException A problem occurs while writing to <code>out</code>
   *
   * @see #readTreeSet
   */
  public static void writeTreeSet(TreeSet<?> set, DataOutput out) throws IOException {
    InternalDataSerializer.checkOut(out);

    int size;

    if (set == null) {
      size = -1;
    } else {
      size = set.size();
    }
    InternalDataSerializer.writeArrayLength(size, out);
    if (logger.isTraceEnabled(LogMarker.SERIALIZER_VERBOSE)) {
      logger.trace(LogMarker.SERIALIZER_VERBOSE, "Writing TreeSet with {} elements: {}", size, set);
    }
    if (size >= 0) {
      writeObject(set.comparator(), out);
      for (Object e : set) {
        writeObject(e, out);
      }
    }
  }

  /**
   * Reads a <code>TreeSet</code> from a <code>DataInput</code>.
   *
   * @throws IOException A problem occurs while reading from <code>in</code>
   * @throws ClassNotFoundException The class of one of the <Code>TreeSet</code>'s elements cannot
   *         be found.
   *
   * @see #writeTreeSet
   */
  public static <E> TreeSet<E> readTreeSet(DataInput in)
      throws IOException, ClassNotFoundException {

    InternalDataSerializer.checkIn(in);

    int size = InternalDataSerializer.readArrayLength(in);
    if (size == -1) {
      return null;
    } else {
      Comparator<? super E> c =
          InternalDataSerializer.readNonPdxInstanceObject(in);
      TreeSet<E> set = new TreeSet<>(c);
      for (int i = 0; i < size; i++) {
        E element = DataSerializer.readObject(in);
        set.add(element);
      }

      if (logger.isTraceEnabled(LogMarker.SERIALIZER_VERBOSE)) {
        logger.trace(LogMarker.SERIALIZER_VERBOSE, "Read TreeSet with {} elements: {}", size, set);
      }

      return set;
    }
  }

  /**
   * Writes a <code>Properties</code> to a <code>DataOutput</code>.
   * <P>
   * NOTE: The <code>defaults</code> of the specified <code>props</code> are not serialized.
   * <p>
   * Note that even though <code>props</code> may be an instance of a subclass of
   * <code>Properties</code>, <code>readProperties</code> will always return an instance of
   * <code>Properties</code>, <B>not</B> an instance of the subclass. To preserve the class type of
   * <code>props</code>, {@link #writeObject(Object, DataOutput)} should be used for data
   * serialization.
   *
   * @throws IOException A problem occurs while writing to <code>out</code>
   *
   * @see #readProperties
   */
  public static void writeProperties(Properties props, DataOutput out) throws IOException {
    InternalDataSerializer.checkOut(out);

    Set<Map.Entry<Object, Object>> s;
    int size;
    if (props == null) {
      s = null;
      size = -1;
    } else {
      s = props.entrySet();
      size = s.size();
    }
    InternalDataSerializer.writeArrayLength(size, out);
    if (logger.isTraceEnabled(LogMarker.SERIALIZER_VERBOSE)) {
      logger.trace(LogMarker.SERIALIZER_VERBOSE, "Writing Properties with {} elements: {}", size,
          props);
    }
    if (size > 0) {
      for (Map.Entry<Object, Object> entry : s) {
        // although we should have just String instances in a Properties
        // object our security code stores byte[] instances in them
        // so the following code must use writeObject instead of writeString.
        Object key = entry.getKey();
        Object val = entry.getValue();
        writeObject(key, out);
        writeObject(val, out);
      }
    }
  }

  /**
   * Reads a <code>Properties</code> from a <code>DataInput</code>.
   * <P>
   * NOTE: the <code>defaults</code> are always empty in the returned <code>Properties</code>.
   *
   *
   * @throws IOException A problem occurs while reading from <code>in</code>
   *
   * @see #writeProperties
   */
  public static Properties readProperties(DataInput in) throws IOException, ClassNotFoundException {

    InternalDataSerializer.checkIn(in);

    int size = InternalDataSerializer.readArrayLength(in);
    if (size == -1) {
      return null;
    } else {
      Properties props = new Properties();
      for (int index = 0; index < size; index++) {
        Object key = readObject(in);
        Object value = readObject(in);
        props.put(key, value);
      }
      if (logger.isTraceEnabled(LogMarker.SERIALIZER_VERBOSE)) {
        logger.trace(LogMarker.SERIALIZER_VERBOSE, "Read Properties with {} elements: {}", size,
            props);
      }
      return props;
    }
  }

  // since 5.7 moved all time unit code to InternalDataSerializer
  // since the TimeUnit class is part of the non-public backport.

  /**
   * Writes an arbitrary object to a <code>DataOutput</code>. If <code>o</code> is not an instance
   * of a specially-handled standard Java class (see the list in {@link #getSupportedClasses}), the
   * {@link DataSerializer#toData toData} method of each registered <code>DataSerializer</code> is
   * invoked until the object is serialized. If no registered serializer can serialize the object
   * and <code>o</code> does not implement <code>DataSerializable</code>, then it is serialized to
   * <code>out</code> using standard Java {@linkplain java.io.Serializable serialization}. This
   * method will serialize a <code>null</code> o and not throw a <code>NullPointerException</code>.
   *
   * @param allowJavaSerialization If false, then a NotSerializableException is thrown in the case
   *        where standard Java serialization would otherwise be used for object <code>o</code> or
   *        for any nested subobject of <code>o</code>. This is used to prevent Java serialization
   *        from being used when sending data to a non-Java client
   * @throws IOException A problem occurs while writing <code>o</code> to <code>out</code>
   *
   * @see #readObject(DataInput)
   * @see Instantiator
   * @see ObjectOutputStream#writeObject
   */
  public static void writeObject(Object o, DataOutput out, boolean allowJavaSerialization)
      throws IOException {

    if (allowJavaSerialization) {
      writeObject(o, out);
      return;
    }

    DISALLOW_JAVA_SERIALIZATION.set(Boolean.TRUE);
    try {
      writeObject(o, out);
    } finally {
      DISALLOW_JAVA_SERIALIZATION.set(Boolean.FALSE); // with JDK 1.5 this can be changed to
                                                      // remove()
    }
  }


  /**
   * Writes an arbitrary object to a <code>DataOutput</code>. If <code>o</code> is not an instance
   * of a specially-handled standard Java class (such as <code>Date</code>, <code>Integer</code>, or
   * <code>ArrayList</code>), the {@link DataSerializer#toData toData} method of each registered
   * <code>DataSerializer</code> is invoked until the object is serialized. If no registered
   * serializer can serialize the object and <code>o</code> does not implement
   * <code>DataSerializable</code>, then it is serialized to <code>out</code> using standard Java
   * {@linkplain java.io.Serializable serialization}. This method will serialize a <code>null</code>
   * o and not throw a <code>NullPointerException</code>.
   *
   * @throws IOException A problem occurs while writing <code>o</code> to <code>out</code>
   *
   * @see #readObject(DataInput)
   * @see DataSerializer
   * @see ObjectOutputStream#writeObject
   */
  public static void writeObject(Object o, DataOutput out) throws IOException {
    InternalDataSerializer.basicWriteObject(o, out, false);
  }

  /**
   * Reads an arbitrary object from a <code>DataInput</code>. Instances of classes that are not
   * handled specially (such as <code>String</code>, <code>Class</code>, and
   * <code>DataSerializable</code>) are read using standard Java {@linkplain java.io.Serializable
   * serialization}.
   *
   * <P>
   *
   * Note that if an object is deserialized using standard Java serialization, its class will be
   * loaded using the current thread's {@link Thread#getContextClassLoader context class loader}
   * before the one normally used by Java serialization is consulted.
   *
   * @throws IOException A problem occurred while reading from <code>in</code> (may wrap another
   *         exception)
   * @throws ClassNotFoundException The class of an object read from <code>in</code> could not be
   *         found
   *
   * @see #writeObject(Object, DataOutput)
   * @see ObjectInputStream#readObject
   */
  @SuppressWarnings("unchecked")
  public static <T> T readObject(final DataInput in) throws IOException, ClassNotFoundException {
    return (T) InternalDataSerializer.basicReadObject(in);
  }

  /**
   * Registers a <code>DataSerializer</code> class with the data serialization framework. This
   * method uses reflection to create an instance of the <code>DataSerializer</code> class by
   * invoking its zero-argument constructor.
   *
   * <P>
   *
   * The <code>DataSerializer</code> instance will be consulted by the
   * {@link #writeObject(Object, DataOutput)} and {@link #readObject} methods. Note that no two
   * serializers can support the same class.
   * <P>
   *
   * This method invokes the <Code>DataSerializer</code> instance's {@link #getSupportedClasses}
   * method and keeps track of which classes can have their instances serialized by by this data
   * serializer.
   *
   * @param c the <code>DataSerializer</code> class to create and register with the data
   *        serialization framework.
   * @return the registered serializer instance
   *
   * @throws IllegalArgumentException If <code>c</code> does not subclass
   *         <code>DataSerializer</code>, if <code>c</code> does not have a zero-argument
   *         constructor, if <code>id</code> is 0, if getSupportedClasses returns null or an empty
   *         array, if getSupportedClasses returns and array with null elements
   * @throws IllegalStateException if another serializer instance with id <code>id</code> has
   *         already been registered, if another serializer instance that supports one of this
   *         instances classes has already been registered, if an attempt is made to support any of
   *         the classes reserved by DataSerializer (see {@link #getSupportedClasses} for a list).
   * @see #getSupportedClasses
   */
  @SuppressWarnings("unchecked")
  public static DataSerializer register(Class<?> c) {
    return InternalDataSerializer.register((Class<? extends DataSerializer>) c, true);
  }

  /**
   * Creates a new <code>DataSerializer</code>. All class that implement <code>DataSerializer</code>
   * must provide a zero-argument constructor.
   *
   * @see #register(Class)
   */
  public DataSerializer() {
    // nothing
  }

  /**
   * Returns the <code>Class</code>es whose instances are data serialized by this
   * <code>DataSerializer</code>. This method is invoked when this serializer is
   * {@linkplain #register(Class) registered}. This method is not allowed to return
   * <code>null</code> nor an empty array. Only instances whose class name is the same as one of the
   * class names in the result will be serialized by this <code>DataSerializer</code>. Two
   * <code>DataSerializer</code>s are not allowed to support the same class. The following classes
   * can not be supported by user defined data serializers since they are all supported by the
   * predefined data serializer:
   * <ul>
   * <li>{@link java.lang.Class}
   * <li>{@link java.lang.String}
   * <li>{@link java.lang.Boolean}
   * <li>{@link java.lang.Byte}
   * <li>{@link java.lang.Character}
   * <li>{@link java.lang.Short}
   * <li>{@link java.lang.Integer}
   * <li>{@link java.lang.Long}
   * <li>{@link java.lang.Float}
   * <li>{@link java.lang.Double}
   * <li>{@link java.io.File}
   * <li>{@link java.net.InetAddress}
   * <li>{@link java.net.Inet4Address}
   * <li>{@link java.net.Inet6Address}
   * <li>{@link java.util.ArrayList}
   * <li>{@link java.util.Date}
   * <li>{@link java.util.HashMap}
   * <li>{@link java.util.IdentityHashMap}
   * <li>{@link java.util.HashSet}
   * <li>{@link java.util.Hashtable}
   * <li>{@link java.util.LinkedHashSet}
   * <li>{@link java.util.LinkedList}
   * <li>{@link java.util.Properties}
   * <li>{@link java.util.TreeMap}
   * <li>{@link java.util.TreeSet}
   * <li>{@link java.util.Vector}
   * <li><code>any array class</code>
   * </ul>
   */
  public abstract Class<?>[] getSupportedClasses();

  /**
   * Data serializes an object to a <code>DataOutput</code>. It is very important that when
   * performing the "switch" on <code>o</code>'s class, your code test for a subclass before it
   * tests for a superclass. Otherwise, the incorrect class id could be written to the serialization
   * stream.
   *
   * @param o The object to data serialize. It will never be <code>null</code>.
   *
   * @return <code>false</code> if this <code>DataSerializer</code> does not know how to data
   *         serialize <code>o</code>.
   */
  public abstract boolean toData(Object o, DataOutput out) throws IOException;

  /**
   * Reads an object from a <code>DataInput</code>. This implementation must support deserializing
   * everything serialized by the matching {@link #toData}.
   *
   * @throws IOException If this serializer cannot read an object from <code>in</code>.
   *
   * @see #toData
   */
  public abstract Object fromData(DataInput in) throws IOException, ClassNotFoundException;

  /**
   * Returns the id of this <code>DataSerializer</code>.
   * <p>
   * Returns an int instead of a byte since 5.7.
   */
  public abstract int getId();

  /**
   * Two <code>DataSerializer</code>s are consider to be equal if they have the same id and the same
   * class
   */
  @Override
  public boolean equals(Object o) {
    if (o instanceof DataSerializer) {
      DataSerializer oDS = (DataSerializer) o;
      return oDS.getId() == getId() && getClass().equals(oDS.getClass());
    } else {
      return false;
    }
  }

  @Override
  public int hashCode() {
    return getId();
  }

  /**
   * For internal use only. Sets the unique <code>eventId</code> of this
   * <code>DataSerializer</code>.
   *
   * @since GemFire 6.5
   */
  public void setEventId(Object/* EventID */ eventId) {
    this.eventId = (EventID) eventId;
  }

  /**
   * For internal use only. Returns the unique <code>eventId</code> of this
   * <code>DataSerializer</code>.
   *
   * @since GemFire 6.5
   */
  public Object/* EventID */ getEventId() {
    return this.eventId;
  }

  /**
   * For internal use only. Sets the context of this <code>DataSerializer</code>.
   *
   * @since GemFire 6.5
   */
  public void setContext(Object/* ClientProxyMembershipID */ context) {
    this.context = (ClientProxyMembershipID) context;
  }

  /**
   * For internal use only. Returns the context of this <code>DataSerializer</code>.
   *
   * @since GemFire 6.5
   */
  public Object/* ClientProxyMembershipID */ getContext() {
    return this.context;
  }

  /**
   * maps a class to its enum constants.
   */
  @MakeNotStatic
  private static final ConcurrentMap<Class<? extends Enum>, Enum[]> knownEnums =
      new ConcurrentHashMap<>();

  /**
   * gets the enum constants for the given class. {@link Class#getEnumConstants()} uses reflection,
   * so we keep around the class to enumConstants mapping in the {@link #knownEnums} map
   *
   * @return enum constants for the given class
   */
  @SuppressWarnings("unchecked")
  private static <E extends Enum> E[] getEnumConstantsForClass(Class<E> clazz) {
    E[] returnVal = (E[]) knownEnums.get(clazz);
    if (returnVal == null) {
      returnVal = clazz.getEnumConstants();
      knownEnums.put(clazz, returnVal);
    }
    return returnVal;
  }

  /**
   * Writes the <code>Enum constant</code> to <code>DataOutput</code>. Unlike standard java
   * serialization which serializes both the enum name String and the ordinal, GemFire only
   * serializes the ordinal byte, so for backward compatibility new enum constants should only be
   * added to the end of the enum type.<br />
   * Example: <code>DataSerializer.writeEnum(DAY_OF_WEEK.SUN, out);</code>
   *
   * @see #readEnum(Class, DataInput)
   * @since GemFire 6.5
   */
  public static void writeEnum(Enum e, DataOutput out) throws IOException {

    InternalDataSerializer.checkOut(out);

    if (e == null) {
      throw new NullPointerException(
          "The enum constant to serialize is null");
    }

    if (logger.isTraceEnabled(LogMarker.SERIALIZER_VERBOSE)) {
      logger.trace(LogMarker.SERIALIZER_VERBOSE, "Writing enum {}", e);
    }
    InternalDataSerializer.writeArrayLength(e.ordinal(), out);
  }

  /**
   * Reads a <code>Enum constant</code> from <code>DataInput</code>. Unlike standard java
   * serialization which serializes both the enum name String and the ordinal, GemFire only
   * serializes the ordinal byte, so be careful about using the correct enum class. Also, for
   * backward compatibility new enum constants should only be added to the end of the enum
   * type.<br />
   * Example: <code>DAY_OF_WEEK d = DataSerializer.readEnum(DAY_OF_WEEK.class, in);</code>
   *
   * @since GemFire 6.5
   * @see #writeEnum(Enum, DataOutput)
   * @throws IOException A problem occurs while writing to <code>out</code>
   * @throws ArrayIndexOutOfBoundsException if the wrong enum class/enum class with a different
   *         version and less enum constants is used
   */
  public static <E extends Enum<E>> E readEnum(Class<E> clazz, DataInput in) throws IOException {

    InternalDataSerializer.checkIn(in);

    if (clazz == null) {
      throw new NullPointerException(
          "the enum class to deserialize is null");
    } else if (!clazz.isEnum()) {
      throw new IllegalArgumentException(
          String.format("Class %s is not an enum", clazz.getName()));
    }

    int ordinal = InternalDataSerializer.readArrayLength(in);

    return getEnumConstantsForClass(clazz)[ordinal];
  }
}
